Files
ladill-meet/app/Services/Meet/CalendarService.php
T
isaaccladandCursor 25de2ed7bc
Deploy Ladill Meet / deploy (push) Successful in 1m10s
Fix Ladill Mail calendar sync mailbox resolution and updates.
Use the connected mail calendar mailbox when set, skip API calls when
MAIL_API_KEY_MEET is unset, and call syncRoomUpdated on room edits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 10:59:59 +00:00

182 lines
6.1 KiB
PHP

<?php
namespace App\Services\Meet;
use App\Models\CalendarConnection;
use App\Models\Room;
use App\Models\User;
use App\Services\Meet\Calendar\GoogleCalendarProvider;
use App\Services\Meet\Calendar\MailCalendarProvider;
use App\Services\Meet\Calendar\MicrosoftCalendarProvider;
use Illuminate\Support\Facades\Log;
class CalendarService
{
public function __construct(
protected GoogleCalendarProvider $google,
protected MicrosoftCalendarProvider $microsoft,
protected MailCalendarProvider $mail,
) {}
public function syncRoomCreated(Room $room, User $host): void
{
if (! $room->scheduled_at) {
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) {
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 ($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;
}
}
}