From 18cff1b067ad0e57da2067864eaf0180e77e8d76 Mon Sep 17 00:00:00 2001 From: Calum Towers Date: Thu, 9 Jan 2025 17:37:32 +0000 Subject: [PATCH 1/4] wip --- .../VatsimNet/ProcessVatsimNetWebhook.php | 28 +++++++ .../Webhooks/MemberChangedAction.php | 74 +++++++++++++++++++ .../Webhooks/MemberCreatedAction.php | 44 +++++++++++ .../Mship/Concerns/HasQualifications.php | 2 +- config/services.php | 7 ++ routes/web-external.php | 16 +--- 6 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php create mode 100644 app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php create mode 100644 app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberCreatedAction.php diff --git a/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php b/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php new file mode 100644 index 0000000000..061627b1ac --- /dev/null +++ b/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php @@ -0,0 +1,28 @@ +header('Authorization') !== config('services.vatsim-net.webhook.key')) { + return response()->json([ + 'status' => 'forbidden', + ], 403); + } + + foreach (request()->json('actions') as $action) { + if (class_exists($class = config("services.vatsim-net.webhook.jobs.{$action['action']}"))) { + dispatch(new $class(request()->json('resource'), $action)); + // ->afterResponse(); + } + } + + return response()->json([ + 'status' => 'ok', + ]); + } +} diff --git a/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php new file mode 100644 index 0000000000..ba60941c63 --- /dev/null +++ b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php @@ -0,0 +1,74 @@ +memberId = $memberId; + $this->data = $data; + } + + public function handle() + { + foreach ($this->data['deltas'] as $delta) { + match ($delta['field']) { + 'id', 'name_first', 'name_last', 'email', 'reg_date' => $this->processAccountChange($delta['field'], $delta['after']), + 'rating' => $this->processAtcRatingChange($delta['after']), + 'pilotrating' => $this->processPilotRatingChange($delta['after']), + 'division_id', 'region_id' => $this->processStateChange($delta['after']), + default => null + }; + } + } + + private function processAccountChange(string $field, mixed $value): void + { + Account::firstWhere('id', $this->memberId)->update([ + $field => $value, + ]); + } + + private function processAtcRatingChange(mixed $value): void + { + Account::firstWhere('id', $this->memberId)->updateVatsimRatings(atcRating: $value); + } + + private function processPilotRatingChange(mixed $value): void + { + Account::firstWhere('id', $this->memberId)->updateVatsimRatings(pilotRating: $value); + } + + private function processStateChange(mixed $value): void + { + // if both a division and region is changed in the deltas + // this will run twice, which is not ideal + + $account = Account::with('states')->firstWhere('id', $this->memberId); + + $currentRegion = $account->primary_permanent_state->pivot->region; + $currentDivision = $account->primary_permanent_state->pivot->division; + + $regionChange = collect($this->data['deltas'])->firstWhere('field', 'region_id'); + $divisionChange = collect($this->data['deltas'])->firstWhere('field', 'division_id'); + + $account->updateDivision( + division: is_null($divisionChange) ? $currentDivision : $divisionChange['after'], + region: is_null($regionChange) ? $currentRegion : $regionChange['after'], + ); + } +} diff --git a/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberCreatedAction.php b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberCreatedAction.php new file mode 100644 index 0000000000..459937654b --- /dev/null +++ b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberCreatedAction.php @@ -0,0 +1,44 @@ +memberId = $memberId; + $this->data = $data; + } + + public function handle() + { + $account = Account::updateOrCreate(['id' => $this->getField('id')], [ + 'name_first' => $this->getField('name_first'), + 'name_last' => $this->getField('name_last'), + 'email' => $this->getField('email'), + 'joined_at' => $this->getField('reg_date'), + ]); + $account->updateVatsimRatings($this->getField('rating'), $this->getField('pilotrating')); + $account->updateDivision($this->getField('division_id'), $this->getField('region_id')); + $account->save(); + } + + private function getField(string $field) + { + return Arr::get(collect($this->data['deltas'])->firstWhere('field', $field), 'after'); + } +} diff --git a/app/Models/Mship/Concerns/HasQualifications.php b/app/Models/Mship/Concerns/HasQualifications.php index ab8875c5d8..b76f5cc07f 100644 --- a/app/Models/Mship/Concerns/HasQualifications.php +++ b/app/Models/Mship/Concerns/HasQualifications.php @@ -78,7 +78,7 @@ public function removeQualification(Qualification $qualification) * @param int|null $atcRating The VATSIM ATC rating * @param int|null $pilotRating The VATSIM pilot rating */ - public function updateVatsimRatings(?int $atcRating, ?int $pilotRating) + public function updateVatsimRatings(?int $atcRating = null, ?int $pilotRating = null) { if ($atcRating === 0) { $this->addNetworkBan('Network ban discovered via Cert login.'); diff --git a/config/services.php b/config/services.php index 04bce23fcc..f27e404d95 100644 --- a/config/services.php +++ b/config/services.php @@ -1,5 +1,8 @@ [ 'webhook' => [ 'key' => env('VATSIM_NET_WEBHOOK_KEY'), + 'jobs' => [ + 'member_created_action' => MemberCreatedAction::class, + 'member_changed_action' => MemberChangedAction::class, + ], ], 'api' => [ 'base' => env('VATSIM_API_BASE', 'https://api.vatsim.net/api/'), diff --git a/routes/web-external.php b/routes/web-external.php index dcab84be90..6b78839f89 100644 --- a/routes/web-external.php +++ b/routes/web-external.php @@ -1,5 +1,7 @@ 'external', 'as' => 'external.', @@ -9,19 +11,7 @@ 'prefix' => 'vatsim-net', 'as' => 'vatsim-net.', ], function () { - - Route::post('webhook', function () { - Log::info(print_r([ - 'Authorization' => request()->header('Authorization'), - 'User-Agent' => request()->header('User-Agent'), - 'Body' => request()->all(), - ], true)); - - return response()->json([ - 'status' => 'ok', - ]); - })->name('webhook'); - + Route::post('webhook', ProcessVatsimNetWebhook::class)->name('webhook'); }); }); From 133c88316cf0e83ca18f629c26a26aefbc22d6f7 Mon Sep 17 00:00:00 2001 From: Calum Towers Date: Thu, 9 Jan 2025 19:21:15 +0000 Subject: [PATCH 2/4] wip --- .../External/VatsimNet/ProcessVatsimNetWebhook.php | 3 ++- .../VatsimNet/Webhooks/MemberChangedAction.php | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php b/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php index 061627b1ac..a9743a7c53 100644 --- a/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php +++ b/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php @@ -15,7 +15,8 @@ public function __invoke() } foreach (request()->json('actions') as $action) { - if (class_exists($class = config("services.vatsim-net.webhook.jobs.{$action['action']}"))) { + $class = config("services.vatsim-net.webhook.jobs.{$action['action']}"); + if ($class && class_exists($class)) { dispatch(new $class(request()->json('resource'), $action)); // ->afterResponse(); } diff --git a/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php index ba60941c63..557e5e85db 100644 --- a/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php +++ b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php @@ -30,7 +30,7 @@ public function handle() 'id', 'name_first', 'name_last', 'email', 'reg_date' => $this->processAccountChange($delta['field'], $delta['after']), 'rating' => $this->processAtcRatingChange($delta['after']), 'pilotrating' => $this->processPilotRatingChange($delta['after']), - 'division_id', 'region_id' => $this->processStateChange($delta['after']), + 'division_id', 'region_id' => $this->processStateChange(), default => null }; } @@ -53,10 +53,9 @@ private function processPilotRatingChange(mixed $value): void Account::firstWhere('id', $this->memberId)->updateVatsimRatings(pilotRating: $value); } - private function processStateChange(mixed $value): void + private function processStateChange(): void { - // if both a division and region is changed in the deltas - // this will run twice, which is not ideal + dump('processing state change'); $account = Account::with('states')->firstWhere('id', $this->memberId); From 1f54d08e96c6e3af8dcd1be1468024932b339574 Mon Sep 17 00:00:00 2001 From: Calum Towers Date: Thu, 9 Jan 2025 19:26:18 +0000 Subject: [PATCH 3/4] wip --- .../VatsimNet/ProcessVatsimNetWebhook.php | 3 +- .../Webhooks/MemberChangedAction.php | 28 ++++++++----------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php b/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php index a9743a7c53..63b1dd243b 100644 --- a/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php +++ b/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php @@ -17,8 +17,7 @@ public function __invoke() foreach (request()->json('actions') as $action) { $class = config("services.vatsim-net.webhook.jobs.{$action['action']}"); if ($class && class_exists($class)) { - dispatch(new $class(request()->json('resource'), $action)); - // ->afterResponse(); + dispatch(new $class(request()->json('resource'), $action))->afterResponse(); } } diff --git a/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php index 557e5e85db..4cb6531fb3 100644 --- a/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php +++ b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php @@ -8,24 +8,24 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Collection; class MemberChangedAction implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected int $memberId; - - protected array $data; + protected Account $account; + protected Collection $data; public function __construct(int $memberId, array $data) { - $this->memberId = $memberId; - $this->data = $data; + $this->account = Account::with('states', 'qualifications')->findOrFail($memberId); + $this->data = collect($data); } public function handle() { - foreach ($this->data['deltas'] as $delta) { + foreach ($this->data->get('deltas') as $delta) { match ($delta['field']) { 'id', 'name_first', 'name_last', 'email', 'reg_date' => $this->processAccountChange($delta['field'], $delta['after']), 'rating' => $this->processAtcRatingChange($delta['after']), @@ -38,34 +38,30 @@ public function handle() private function processAccountChange(string $field, mixed $value): void { - Account::firstWhere('id', $this->memberId)->update([ + $this->account->update([ $field => $value, ]); } private function processAtcRatingChange(mixed $value): void { - Account::firstWhere('id', $this->memberId)->updateVatsimRatings(atcRating: $value); + $this->account->updateVatsimRatings(atcRating: $value); } private function processPilotRatingChange(mixed $value): void { - Account::firstWhere('id', $this->memberId)->updateVatsimRatings(pilotRating: $value); + $this->account->updateVatsimRatings(pilotRating: $value); } private function processStateChange(): void { - dump('processing state change'); - - $account = Account::with('states')->firstWhere('id', $this->memberId); - - $currentRegion = $account->primary_permanent_state->pivot->region; - $currentDivision = $account->primary_permanent_state->pivot->division; + $currentRegion = $this->account->primary_permanent_state->pivot->region; + $currentDivision = $this->account->primary_permanent_state->pivot->division; $regionChange = collect($this->data['deltas'])->firstWhere('field', 'region_id'); $divisionChange = collect($this->data['deltas'])->firstWhere('field', 'division_id'); - $account->updateDivision( + $this->account->updateDivision( division: is_null($divisionChange) ? $currentDivision : $divisionChange['after'], region: is_null($regionChange) ? $currentRegion : $regionChange['after'], ); From 3d2e42605039afc9ef864d6e966f81121670ac21 Mon Sep 17 00:00:00 2001 From: Calum Towers Date: Thu, 9 Jan 2025 19:29:09 +0000 Subject: [PATCH 4/4] Update MemberChangedAction.php --- .../ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php index 4cb6531fb3..2c952d03c5 100644 --- a/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php +++ b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php @@ -15,6 +15,7 @@ class MemberChangedAction implements ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected Account $account; + protected Collection $data; public function __construct(int $memberId, array $data)