Files
ladill-events/app/Services/Meet/EventMeetSyncService.php
isaaccladandCursor 6cf069d630
Deploy Ladill Events / deploy (push) Successful in 31s
Return programme snapshot from Events link-room API.
Meet can persist the agenda locally when linking, even if the service-to-service Meet sync fails.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 22:25:12 +00:00

214 lines
7.2 KiB
PHP

<?php
namespace App\Services\Meet;
use App\Models\QrCode;
use App\Models\User;
use App\Services\Events\EventProgrammeService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EventMeetSyncService
{
public function __construct(private readonly EventProgrammeService $programmes) {}
public function sync(QrCode $event, User $owner): QrCode
{
if ($event->type !== QrCode::TYPE_EVENT) {
return $event;
}
$content = $event->content();
$format = (string) ($content['format'] ?? 'in_person');
if (! in_array($format, ['virtual', 'hybrid'], true)) {
$this->cancelRemovedSessions($event, $owner, []);
return $event;
}
if ((string) config('meet.key', '') === '') {
return $event;
}
$programmeSnapshot = $this->programmeSnapshotFor($event);
$sessions = (array) ($content['virtual_sessions'] ?? []);
$client = MeetClient::for($owner->public_id);
$synced = [];
$activeIds = [];
foreach ($sessions as $session) {
if (! is_array($session)) {
continue;
}
$sessionId = trim((string) ($session['id'] ?? ''));
if ($sessionId === '') {
$sessionId = 'vs-'.Str::lower(Str::random(12));
}
$activeIds[] = $sessionId;
$title = trim((string) ($session['title'] ?? ''));
if ($title === '') {
$synced[] = array_merge($session, ['id' => $sessionId]);
continue;
}
$roomType = in_array($session['meet_room_type'] ?? '', ['town_hall', 'webinar'], true)
? (string) $session['meet_room_type']
: 'town_hall';
$scheduledAt = $session['scheduled_at'] ?? $content['starts_at'] ?? null;
$itemRefs = (array) ($session['programme_item_refs'] ?? []);
$linkedItems = $this->programmes->itemsForRefs($programmeSnapshot, $itemRefs);
$inviteEmails = $this->inviteEmailsFromHosts($this->programmes->hostsFromItems($linkedItems));
$settings = [
'programme_snapshot' => $programmeSnapshot,
'linked_programme_items' => $linkedItems,
];
if ($roomType === 'town_hall' && $linkedItems !== []) {
$settings['panels'] = [[
'id' => 'panel-'.$sessionId,
'name' => $title,
'speaker_refs' => [],
]];
$settings['panel_discussions'] = true;
}
$roomPayload = [
'title' => $title,
'description' => trim((string) ($content['description'] ?? '')) ?: null,
'type' => $roomType,
'scheduled_at' => $scheduledAt,
'duration_minutes' => max(15, (int) ($session['duration_minutes'] ?? 60)),
'timezone' => config('app.timezone', 'UTC'),
'settings' => $settings,
'invite_emails' => $inviteEmails,
'source' => [
'app' => 'events',
'entity_type' => 'virtual_session',
'entity_id' => $sessionId,
'event_id' => (string) $event->id,
],
];
$existingRoomUuid = trim((string) ($session['meet_room_uuid'] ?? ''));
try {
$result = $existingRoomUuid !== ''
? $client->updateRoom($existingRoomUuid, $roomPayload)
: $client->upsertRoom($roomPayload);
} catch (\Throwable $e) {
Log::warning('Events Meet sync failed', [
'event_id' => $event->id,
'session_id' => $sessionId,
'error' => $e->getMessage(),
]);
$synced[] = array_merge($session, ['id' => $sessionId]);
continue;
}
$room = (array) ($result['room'] ?? []);
$synced[] = array_merge($session, [
'id' => $sessionId,
'meet_room_uuid' => $room['uuid'] ?? $session['meet_room_uuid'] ?? null,
'join_url' => $result['join_url'] ?? $session['join_url'] ?? null,
]);
}
$this->cancelRemovedSessions($event, $owner, $activeIds);
$payload = (array) ($event->payload ?? []);
$payload['content'] = array_merge($content, ['virtual_sessions' => array_values($synced)]);
$event->payload = $payload;
$event->save();
return $event->fresh();
}
public function syncEventsForProgramme(QrCode $programme, User $owner): void
{
foreach ($this->programmes->eventsLinkedToProgramme($programme) as $event) {
$this->sync($event->fresh(), $owner);
}
}
/**
* @param array<string, mixed> $session
* @return array{programme_snapshot: array<string, mixed>|null, linked_programme_items: list<array<string, mixed>>}
*/
public function programmePayloadForSession(QrCode $event, array $session): array
{
$programmeSnapshot = $this->programmeSnapshotFor($event);
$itemRefs = (array) ($session['programme_item_refs'] ?? []);
return [
'programme_snapshot' => $programmeSnapshot,
'linked_programme_items' => $this->programmes->itemsForRefs($programmeSnapshot, $itemRefs),
];
}
/** @return array<string, mixed>|null */
private function programmeSnapshotFor(QrCode $event): ?array
{
$programmeId = (int) ($event->content()['programme_qr_id'] ?? 0);
if ($programmeId <= 0) {
return null;
}
$programme = QrCode::where('id', $programmeId)
->where('user_id', $event->user_id)
->where('type', QrCode::TYPE_ITINERARY)
->first();
return $this->programmes->snapshot($programme);
}
/** @param list<string> $hosts */
private function inviteEmailsFromHosts(array $hosts): array
{
return collect($hosts)
->filter(fn ($host) => filter_var($host, FILTER_VALIDATE_EMAIL))
->values()
->all();
}
/** @param list<string> $keepIds */
private function cancelRemovedSessions(QrCode $event, User $owner, array $keepIds): void
{
if ((string) config('meet.key', '') === '') {
return;
}
$previous = (array) ($event->content()['virtual_sessions'] ?? []);
$client = MeetClient::for($owner->public_id);
foreach ($previous as $session) {
if (! is_array($session)) {
continue;
}
$sessionId = (string) ($session['id'] ?? '');
$roomUuid = (string) ($session['meet_room_uuid'] ?? '');
if ($sessionId === '' || in_array($sessionId, $keepIds, true) || $roomUuid === '') {
continue;
}
try {
$client->cancelRoom($roomUuid);
} catch (\Throwable $e) {
Log::warning('Events Meet cancel failed', [
'event_id' => $event->id,
'session_id' => $sessionId,
'error' => $e->getMessage(),
]);
}
}
}
}