From 13eba88a6f99ffcb1c51354509b81a71a1608879 Mon Sep 17 00:00:00 2001 From: Zachary Craig Date: Thu, 28 Dec 2023 10:23:12 -0500 Subject: [PATCH] Add petfinder stuff --- app/Console/Commands/LostPetsImport.php | 149 ++++++++++++++++++ app/Console/Kernel.php | 3 +- app/Models/LostPet.php | 15 ++ app/Providers/GeocoderProvider.php | 42 +++++ config/app.php | 1 + config/sentry.php | 84 ++++++++++ config/services.php | 9 ++ ...23_12_28_001437_create_lost_pets_table.php | 47 ++++++ .../components/partials/pet-card.blade.php | 9 ++ resources/views/pages/lost-pets.blade.php | 21 +++ routes/web.php | 12 ++ 11 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/LostPetsImport.php create mode 100644 app/Models/LostPet.php create mode 100644 app/Providers/GeocoderProvider.php create mode 100644 config/sentry.php create mode 100644 database/migrations/2023_12_28_001437_create_lost_pets_table.php create mode 100644 resources/views/components/partials/pet-card.blade.php create mode 100644 resources/views/pages/lost-pets.blade.php diff --git a/app/Console/Commands/LostPetsImport.php b/app/Console/Commands/LostPetsImport.php new file mode 100644 index 0000000..9223237 --- /dev/null +++ b/app/Console/Commands/LostPetsImport.php @@ -0,0 +1,149 @@ +geocode(config('services.petco.location')); + $data = data_get($response, 'results.0', false); + $petCount = 0; + for ($i = 0; $i < 90; $i++) { + $pets = collect( + $this->getPage($data['lat'], + $data['lon'], + $i, + config('services.petco.search_radius') + ) + )->filter(function($item){ + return $item['typeKey'] != 'search.headerRow.foundPetNearbyList'; + }); + $thisPageCount = count($pets); + foreach ($pets as $pet) { + $this->createOrUpdatePet($pet); + $petCount++; + } + $this->info("Page $i: $thisPageCount pets"); + sleep(10); + } + $this->info("Imported $petCount pets"); + return CommandAlias::SUCCESS; + } + + private function getPage($lat, $long, $page, $type = ['Cat'], $radius = 100) + { + $response = Http::asJson()->withHeaders([ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0', + 'X-Parse-Application-Id' => config('services.petco.application_id'), + 'X-Parse-Session-Token' => config('services.petco.session_token') + ])->post('https://frbackendprod.herokuapp.com/parse/functions/getSearchSet', [ + 'objects' => [ + 'geopoint' => [ + 'latitude' => $lat, + 'longitude' => $long, + ] + ], + 'parameters' => [ + 'appVersion' => '100000', + 'facialEngine' => 'MLFacial', + 'feedSetTypeKey' => 'getNearbyList', + 'filters' => $type, + 'listDistance' => "$radius", + 'mlFacialParameters' => [ + 'paging' => [ + 'limit' => 25, + 'skip' => 0, + ], + ], + 'mode' => 'list', + 'osname' => 'webVue', + 'pageNum' => $page, + 'typeKeys' => [ + 0 => 'foundPet', + 1 => 'foundOrgPet' + ] + ] + ]); + return $response->json('result.results.objects.items'); + } + + private function createOrUpdatePet($pet) : void + { + match ($pet['typeKey']) { + 'foundPet' => $this->importFoundPet($pet), + 'foundOrgPet' => $this->importFoundOrgPet($pet), + default => $this->info("Unknown type: {$pet['typeKey']}") + }; + } + + + private function importFoundPet($pet){ + return LostPet::query()->firstOrCreate([ + 'animal_id' => data_get($pet, 'id') + ], [ + 'name' => data_get($pet, 'name', 'Unknown Name'), + 'breed' => data_get($pet, '', 'Unknown Breed'), + 'color' => data_get($pet, 'targetEntity.attributes.FDRAnimalPrimaryColor', 'Unknown Color'), + 'sex' => data_get($pet, 'attributes.FDRPetSex', 'Unknown Sex'), + 'photo' => data_get($pet, 'photo.url'), + 'age' => data_get($pet, 'targetEntity.attributes.FDRAnimalAge'), + 'age_group' => data_get($pet, 'targetEntity.attributes.FDRAnimalAgeGroup', 'Unknown Age Group'), + 'status' => data_get($pet, 'targetEntity.status', 'Unknown Status'), + 'rescue_name' => data_get($pet, 'fromEntity.name', 'Unknown Name'), + 'rescue_address' => data_get($pet, 'fromEntity.attributes.locationAddress'), + 'rescue_email' => trim(data_get($pet, 'fromEntity.attributes.FDREntityEmail')), + 'rescue_phone' => data_get($pet, 'fromEntity.attributes.FDREntityPhone'), + 'intake_date' => Carbon::parse(data_get($pet, 'attributes.FDRPetPosterStartDate')), + 'intake_type' => data_get($pet, 'targetEntity.attributes.FDRAnimalIntakeTypeKey'), + 'poster_text' => data_get($pet, 'attributes.FDRPetPosterMarkings') + ]); + } + + private function importFoundOrgPet($pet){ + return LostPet::query()->firstOrCreate([ + 'animal_id' => data_get($pet, 'id') + ], [ + 'name' => data_get($pet, 'targetEntity.name', 'No name'), + 'breed' => data_get($pet, 'targetEntity.attributes.FDRDogPrimaryBreed', 'Unknown Breed'), + 'color' => data_get($pet, 'targetEntity.attributes.FDRAnimalPrimaryColor', 'Unknown Color'), + 'sex' => data_get($pet, 'targetEntity.attributes.FDRPetSex', 'Unknown'), + 'photo' => data_get($pet, 'targetEntity.photoOriginal.url'), + 'age' => data_get($pet, 'targetEntity.attributes.FDRAnimalAge'), + 'age_group' => data_get($pet, 'targetEntity.attributes.FDRAnimalAgeGroup', 'Unknown Age Group'), + 'status' => data_get($pet, 'targetEntity.status', 'Unknown Status'), + 'rescue_name' => data_get($pet, 'fromEntity.name', 'No name'), + 'rescue_email' => trim(data_get($pet, 'fromEntity.attributes.FDREntityEmail')), + 'rescue_phone' => data_get($pet, 'fromEntity.attributes.FDREntityPhone'), + 'intake_date' => Carbon::parse(data_get($pet, 'targetEntity.attributes.FDRAnimalIntakeDate')), + 'intake_type' => data_get($pet, 'targetEntity.attributes.FDRAnimalIntakeTypeKey'), + ]); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 97a4a3b..59daa78 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -24,10 +24,9 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - // $schedule->command('inspire') - // ->hourly(); $schedule->command('upload:scan')->everyMinute(); $schedule->command('logbook:import')->daily(); + $schedule->command('lost-pets:import')->hourly(); } /** diff --git a/app/Models/LostPet.php b/app/Models/LostPet.php new file mode 100644 index 0000000..81df029 --- /dev/null +++ b/app/Models/LostPet.php @@ -0,0 +1,15 @@ + 'datetime', + ]; +} diff --git a/app/Providers/GeocoderProvider.php b/app/Providers/GeocoderProvider.php new file mode 100644 index 0000000..674dc07 --- /dev/null +++ b/app/Providers/GeocoderProvider.php @@ -0,0 +1,42 @@ +baseUrl = 'https://api.geoapify.com/'; + $this->apiKey = config('services.geoapify.key'); + } + + /** + * Register services. + * + * @return void + */ + public function register() + { + $this->app->singleton(GeocoderProvider::class, function ($app) { + return new GeocoderProvider($app); + }); + } + + public function geocode($address){ + if(\Cache::has(sha1($address))){ + return \Cache::get(sha1($address)); + } + $value = Http::get('https://api.geoapify.com/v1/geocode/search', [ + 'text' => $address, + 'format' => 'json', + 'apiKey' => $this->apiKey, + ])->json(); + \Cache::put(sha1($address), $value, now()->addDay()); + return $value; + } +} diff --git a/config/app.php b/config/app.php index 1f86ff0..219d847 100644 --- a/config/app.php +++ b/config/app.php @@ -179,6 +179,7 @@ \App\Providers\MainheadGridResolutionServiceProvider::class, \App\Providers\ParksOnTheAirServiceProvider::class, \App\Providers\QRZLogbookProvider::class, + \App\Providers\GeocoderProvider::class, ], /* diff --git a/config/sentry.php b/config/sentry.php new file mode 100644 index 0000000..bbf98c8 --- /dev/null +++ b/config/sentry.php @@ -0,0 +1,84 @@ + env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')), + + // The release version of your application + // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) + 'release' => env('SENTRY_RELEASE'), + + // When left empty or `null` the Laravel environment will be used + 'environment' => env('SENTRY_ENVIRONMENT'), + + 'breadcrumbs' => [ + // Capture Laravel logs in breadcrumbs + 'logs' => true, + + // Capture Laravel cache events in breadcrumbs + 'cache' => true, + + // Capture Livewire components in breadcrumbs + 'livewire' => true, + + // Capture SQL queries in breadcrumbs + 'sql_queries' => true, + + // Capture bindings on SQL queries logged in breadcrumbs + 'sql_bindings' => true, + + // Capture queue job information in breadcrumbs + 'queue_info' => true, + + // Capture command information in breadcrumbs + 'command_info' => true, + + // Capture HTTP client requests information in breadcrumbs + 'http_client_requests' => true, + ], + + 'tracing' => [ + // Trace queue jobs as their own transactions + 'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', false), + + // Capture queue jobs as spans when executed on the sync driver + 'queue_jobs' => true, + + // Capture SQL queries as spans + 'sql_queries' => true, + + // Try to find out where the SQL query originated from and add it to the query spans + 'sql_origin' => true, + + // Capture views as spans + 'views' => true, + + // Capture Livewire components as spans + 'livewire' => true, + + // Capture HTTP client requests as spans + 'http_client_requests' => true, + + // Capture Redis operations as spans (this enables Redis events in Laravel) + 'redis_commands' => env('SENTRY_TRACE_REDIS_COMMANDS', false), + + // Try to find out where the Redis command originated from and add it to the command spans + 'redis_origin' => true, + + // Indicates if the tracing integrations supplied by Sentry should be loaded + 'default_integrations' => true, + + // Indicates that requests without a matching route should be traced + 'missing_routes' => false, + ], + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#send-default-pii + 'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false), + + // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces-sample-rate + 'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float)env('SENTRY_TRACES_SAMPLE_RATE'), + + 'profiles_sample_rate' => env('SENTRY_PROFILES_SAMPLE_RATE') === null ? null : (float)env('SENTRY_PROFILES_SAMPLE_RATE'), + +]; diff --git a/config/services.php b/config/services.php index 2c803dd..64af948 100644 --- a/config/services.php +++ b/config/services.php @@ -59,5 +59,14 @@ 'discord' => [ 'webhook_uri' => env('DISCORD_WEBHOOK_URI') + ], + 'petco' => [ + 'application_id' => 'z1dq4rokCsjt7foJ4C6uMzN4YhSFgsSzbAQlBY2y', + 'session_token' => env('PETCO_SESSION_TOKEN'), + 'location' => env('PECO_LOCATION'), + 'search_radius' => env('PETCO_SEARCH_RADIUS', 100) + ], + 'geoapify' => [ + 'key' => env('GEOAPIFY_API_KEY') ] ]; diff --git a/database/migrations/2023_12_28_001437_create_lost_pets_table.php b/database/migrations/2023_12_28_001437_create_lost_pets_table.php new file mode 100644 index 0000000..9a6696d --- /dev/null +++ b/database/migrations/2023_12_28_001437_create_lost_pets_table.php @@ -0,0 +1,47 @@ +id(); + $table->string('animal_id'); + $table->string('name')->nullable(); + $table->string('breed')->nullable(); + $table->string('color')->nullable(); + $table->string('sex')->nullable(); + $table->string('photo')->nullable(); + $table->integer('age')->nullable(); + $table->string('age_group')->nullable(); + $table->string('status')->nullable(); + $table->string('rescue_name')->nullable(); + $table->string('rescue_address')->nullable(); + $table->string('rescue_email')->nullable(); + $table->string('rescue_phone')->nullable(); + $table->dateTime('intake_date')->nullable(); + $table->string('intake_type')->nullable(); + $table->longText("poster_text")->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('lost_pets'); + } +}; diff --git a/resources/views/components/partials/pet-card.blade.php b/resources/views/components/partials/pet-card.blade.php new file mode 100644 index 0000000..952f0c0 --- /dev/null +++ b/resources/views/components/partials/pet-card.blade.php @@ -0,0 +1,9 @@ +
+ + + + {{$pet->name}}
+ Rescue: {{$pet->rescue_name}}
+ Found: {{$pet->intake_date->diffForHumans()}}
+ view +
diff --git a/resources/views/pages/lost-pets.blade.php b/resources/views/pages/lost-pets.blade.php new file mode 100644 index 0000000..3a7a840 --- /dev/null +++ b/resources/views/pages/lost-pets.blade.php @@ -0,0 +1,21 @@ +@extends('layouts.app') +@section('title', 'Lost Pets') +@push('styles') + +@endpush +@section('content') +
+
+

Lost Pets

+
+
+ @foreach($pets as $pet) + @include('components.partials.pet-card', ['pet' => $pet]) + @endforeach +
+
+@endsection diff --git a/routes/web.php b/routes/web.php index bcdd22b..c279f3f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,6 +14,9 @@ */ +use App\Models\LostPet; +use Carbon\Carbon; + Route::get('/', 'PageController@home')->name("home"); Route::get('/photos', 'PageController@photos')->name('photography'); Route::get('/qsos', 'PageController@qsos')->name('qsos'); @@ -43,3 +46,12 @@ return redirect(route('file.show', ['file' => $file_name]), 301); }); +Route::get('/lost-pets', function(){ + return view('pages.lost-pets', [ + 'pets' => LostPet::query() + ->where('intake_date', '>=', Carbon::parse('2023-12-21')->startOfDay()) + ->orderByDesc('intake_date') + ->get() + ]); +}); +