connectionFor($host); if (! $connection) { return; } $eventId = $this->provider($connection)->createEvent($connection, $room, $host); if ($eventId) { $room->update(['calendar_event_id' => $eventId]); } } public function syncRoomUpdated(Room $room, User $host): void { $connection = $this->connectionFor($host); if (! $connection || ! $room->calendar_event_id) { return; } $this->provider($connection)->updateEvent($connection, $room, $host); } public function syncRoomCancelled(Room $room, User $host): void { $connection = $this->connectionFor($host); if (! $connection || ! $room->calendar_event_id) { return; } $this->provider($connection)->deleteEvent($connection, $room); $room->update(['calendar_event_id' => null]); } public function connectionFor(User $user, ?string $provider = null): ?CalendarConnection { $query = CalendarConnection::where('user_ref', $user->ownerRef()); if ($provider) { $query->where('provider', $provider); } return $query->orderByDesc('updated_at')->first(); } protected function provider(CalendarConnection $connection): GoogleCalendarProvider|MicrosoftCalendarProvider|MailCalendarProvider { return match ($connection->provider) { 'microsoft' => $this->microsoft, 'mail' => $this->mail, default => $this->google, }; } public function connectMail(User $user, int $organizationId, ?string $mailboxEmail = null): CalendarConnection { $email = strtolower(trim($mailboxEmail ?: (string) $user->email)); return CalendarConnection::updateOrCreate( ['user_ref' => $user->ownerRef(), 'provider' => 'mail'], [ 'owner_ref' => $user->ownerRef(), 'organization_id' => $organizationId, 'calendar_id' => $email, 'access_token' => null, 'refresh_token' => null, 'expires_at' => null, 'metadata' => ['source' => 'ladill_mail'], ], ); } public function storeTokens(User $user, int $organizationId, string $provider, array $tokens): CalendarConnection { return CalendarConnection::updateOrCreate( ['user_ref' => $user->ownerRef(), 'provider' => $provider], [ 'owner_ref' => $user->ownerRef(), 'organization_id' => $organizationId, 'access_token' => $tokens['access_token'] ?? null, 'refresh_token' => $tokens['refresh_token'] ?? null, 'expires_at' => isset($tokens['expires_in']) ? now()->addSeconds((int) $tokens['expires_in']) : null, 'calendar_id' => $tokens['calendar_id'] ?? 'primary', 'metadata' => $tokens['metadata'] ?? null, ], ); } public function oauthRedirectUrl(string $provider): ?string { return match ($provider) { 'google' => $this->google->authorizeUrl(), 'microsoft' => $this->microsoft->authorizeUrl(), default => null, }; } public function handleOAuthCallback(string $provider, string $code, User $user, int $organizationId): ?CalendarConnection { try { $tokens = match ($provider) { 'google' => $this->google->exchangeCode($code), 'microsoft' => $this->microsoft->exchangeCode($code), default => null, }; if (! $tokens) { return null; } return $this->storeTokens($user, $organizationId, $provider, $tokens); } catch (\Throwable $e) { Log::warning('Calendar OAuth failed', ['provider' => $provider, 'error' => $e->getMessage()]); return null; } } }