diff --git a/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php b/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php new file mode 100644 index 0000000000..60a82e454c --- /dev/null +++ b/app/Http/Controllers/External/VatsimNet/ProcessVatsimNetWebhook.php @@ -0,0 +1,40 @@ +header('Authorization') !== config('services.vatsim-net.webhook.key')) { + return response()->json([ + 'status' => 'forbidden', + ], 403); + } + + foreach (request()->json('actions') as $action) { + $class = match ($action['action']) { + 'member_created_action' => MemberCreatedAction::class, + 'member_changed_action' => MemberChangedAction::class, + default => null, + }; + + if (! $class) { + Log::error("Unhandled webhook from VATSIM.net: {$action['action']}"); + + continue; + } + + dispatch(new $class(request()->json('resource'), $action)); + } + + 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..2c952d03c5 --- /dev/null +++ b/app/Jobs/ExternalServices/VatsimNet/Webhooks/MemberChangedAction.php @@ -0,0 +1,70 @@ +account = Account::with('states', 'qualifications')->findOrFail($memberId); + $this->data = collect($data); + } + + public function handle() + { + 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']), + 'pilotrating' => $this->processPilotRatingChange($delta['after']), + 'division_id', 'region_id' => $this->processStateChange(), + default => null + }; + } + } + + private function processAccountChange(string $field, mixed $value): void + { + $this->account->update([ + $field => $value, + ]); + } + + private function processAtcRatingChange(mixed $value): void + { + $this->account->updateVatsimRatings(atcRating: $value); + } + + private function processPilotRatingChange(mixed $value): void + { + $this->account->updateVatsimRatings(pilotRating: $value); + } + + private function processStateChange(): void + { + $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'); + + $this->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/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'); }); });