scheduled_at || $room->isEventsSourced()) { return; } if ($mailbox = $this->resolveMailMailbox($host)) { $eventId = $this->mail->createEventForMailbox($mailbox, $room); if ($eventId) { $room->update(['calendar_event_id' => $eventId]); } } $connection = $this->connectionFor($host); if ($connection && in_array($connection->provider, ['google', 'microsoft'], true)) { $this->provider($connection)->createEvent($connection, $room, $host); } } public function syncRoomUpdated(Room $room, User $host): void { if (! $room->scheduled_at || $room->isEventsSourced()) { return; } if ($mailbox = $this->resolveMailMailbox($host)) { if ($room->calendar_event_id) { $this->mail->updateEventForMailbox($mailbox, $room); } else { $this->syncRoomCreated($room, $host); } } $connection = $this->connectionFor($host); if ($connection && $room->calendar_event_id && in_array($connection->provider, ['google', 'microsoft'], true)) { $this->provider($connection)->updateEvent($connection, $room, $host); } } public function syncRoomCancelled(Room $room, User $host): void { if ($room->isEventsSourced()) { return; } if ($mailbox = $this->resolveMailMailbox($host)) { if ($room->calendar_event_id) { $this->mail->deleteEventForMailbox($mailbox, $room); } } $connection = $this->connectionFor($host); if ($connection && $room->calendar_event_id && in_array($connection->provider, ['google', 'microsoft'], true)) { $this->provider($connection)->deleteEvent($connection, $room); } $room->update(['calendar_event_id' => null]); } public function mailCalendarConfigured(): bool { return filled(config('meet.calendar.mail.api_key')) && filled(config('meet.calendar.mail.api_url')); } protected function resolveMailMailbox(User $host): ?string { if (! $this->mailCalendarConfigured()) { return null; } $connection = $this->connectionFor($host, 'mail'); if ($connection?->calendar_id) { return strtolower(trim((string) $connection->calendar_id)); } $email = strtolower(trim((string) $host->email)); return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : 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; } } }