Add virtual event sessions with Meet sync and platform-billed messaging.
Deploy Ladill Events / deploy (push) Successful in 41s

Events can define hybrid/virtual sessions synced to Meet rooms; SMS and email use wallet-billed platform APIs instead of Termii and Laravel mail.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-01 22:08:09 +00:00
co-authored by Cursor
parent 5a8a462102
commit 06fedcfc55
18 changed files with 735 additions and 30 deletions
+137
View File
@@ -0,0 +1,137 @@
<?php
namespace App\Services\Meet;
use App\Models\QrCode;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EventMeetSyncService
{
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;
}
$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;
try {
$result = $client->upsertRoom([
'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'),
'source' => [
'app' => 'events',
'entity_type' => 'virtual_session',
'entity_id' => $sessionId,
'event_id' => (string) $event->id,
],
]);
} 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();
}
/** @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(),
]);
}
}
}
}