Complete Events–Meet integration phases 2–5.
Deploy Ladill Events / deploy (push) Successful in 41s

Programme sync to Meet rooms, wallet-billed mass comms, webhook lifecycle updates, and registration access bridge with join-window gating on public pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-01 22:31:45 +00:00
co-authored by Cursor
parent 06fedcfc55
commit 05a6be7efe
20 changed files with 798 additions and 115 deletions
@@ -4,11 +4,14 @@ 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) {
@@ -28,6 +31,7 @@ class EventMeetSyncService
return $event;
}
$programmeSnapshot = $this->programmeSnapshotFor($event);
$sessions = (array) ($content['virtual_sessions'] ?? []);
$client = MeetClient::for($owner->public_id);
$synced = [];
@@ -56,6 +60,23 @@ class EventMeetSyncService
: '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;
}
try {
$result = $client->upsertRoom([
@@ -65,6 +86,8 @@ class EventMeetSyncService
'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',
@@ -101,6 +124,38 @@ class EventMeetSyncService
return $event->fresh();
}
public function syncEventsForProgramme(QrCode $programme, User $owner): void
{
foreach ($this->programmes->eventsLinkedToProgramme($programme) as $event) {
$this->sync($event->fresh(), $owner);
}
}
/** @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
{