Skip to content

Meetings — Developer Guide

Status: Implemented
Required dependency: Calendar. Meetings owns booking, host, attendees, providers, and reminders; Calendar owns the projected event.

Ownership

ConceptOwner
meetings, attendees, reminders, provider connectionsMeetings module
Zoom / Google Meet OAuth + remote meeting syncMeetings module
Calendar projection (source=meeting)CalendarEventService::upsertFromSource

Domain

  • App\Models\MeetingBelongsToTenant, LogsActivity, SoftDeletes
  • MeetingAttendee — internal user_id and/or external email/name
  • MeetingProviderConnection — encrypted tokens, one connection per provider per tenant
  • MeetingReminder — single durable reminder row per meeting

Enums: MeetingStatusEnum, MeetingProviderEnum, MeetingAttendeeRoleEnum, MeetingProviderConnectionStatusEnum, MeetingReminderStatusEnum, MeetingProviderSyncStatusEnum.

Morph alias: meetingApp\Models\Meeting (registered in AppServiceProvider).

Timezone

Follow the platform Workspace timezone convention: one Settings → General timezone for wall clocks; no per-module timezone.

starts_at / ends_at / cancelled_at / completed_at / reminder remind_at use App\Casts\UtcDateTime (DB = UTC instants). MeetingResource serializes them with App\Support\UtcIso. The SPA form locks timezone to the workspace setting (falling back to the meeting’s stored timezone when Settings are still on the UTC shell default) and converts wall-clock ↔ UTC via src/lib/datetime.ts. List/detail formatters pass that same resolved timezone into formatAppDateTime.

Service contract

App\Services\Tenant\MeetingService is the sole writer:

  • CRUD, complete, cancel, assign host, attendee sync
  • completePastMeetings() for scheduler auto-complete
  • Provider sync via MeetingProviderRegistry (none / Zoom / Google Meet; fake drivers in testing)
  • Calendar projection / cancel / delete (meeting completed maps to calendar scheduled)
  • Reminder schedule / reschedule / cancel

Status transitions: scheduledcompleted (manual or auto) or cancelled. Completed/cancelled meetings are not mutable.

Providers

  • Interface: App\Contracts\Meetings\MeetingProviderInterface (authorize, exchange, refreshAccessToken, create/update/delete)
  • Registry: App\Services\Meetings\MeetingProviderRegistry
  • Per-tenant BYOK: each workspace stores its own OAuth client_id / client_secret / optional webhook_secret (encrypted) on MeetingProviderConnection
  • OAuth account connect: MeetingIntegrationController (meetings.manage_integrations) with one-time cache nonce in state
  • Platform-hosted callback: GET /api/oauth/meetings/{provider}/callback (tenant + nonce from encrypted state; re-checks manage_integrations)
  • Access tokens refresh automatically near expiry (Zoom + Google)
  • Google Meet creates a Meet space join link (not a Google Calendar conference); update is a no-op; delete removes the space when possible
  • Webhooks: MeetingWebhookController is a stub (custom HMAC ack only — not Zoom/Google-native event ingestion). Do not rely on it for production reconciliation yet.
  • Retry: RetryProviderSyncJob (tenant id + Laravel $tries/$backoff; does not re-dispatch forever from syncProvider)

Config: config/meetings.php (fake flag + Google/Zoom OAuth scopes + callback path only — no platform client secrets). Fake drivers are forced off in production. Prefer fail-closed on create when provider ≠ none and account is not connected. Remote provider delete on cancel/delete is best-effort (local status always wins; failures land on sanitized provider_sync_*). Manual retry: POST /meetings/{id}/retry-sync.

Notifications

TypeChannels
meeting.invite / meeting.updated / meeting.cancelledmail + database + broadcast + web push (users); mail-only for external guests
meeting.remindersame; dispatched by crm:send-due-notifications

Subscriber: MeetingEventSubscriber (audit + notifications; skips actor for invite/update/cancel). MeetingCompleted is audit-only (no attendee email).

Scheduler

  • crm:send-due-notifications (every 5 minutes, withoutOverlapping, onOneServer) claims due MeetingReminder rows atomically (pendingsendingsent) with NotificationIdempotency for users and cache dedupe for external guest mail. Only reminders for scheduled meetings are sent.
  • meetings:auto-complete (every 5 minutes, withoutOverlapping, onOneServer) marks scheduled meetings with ends_at <= now() as completed.

Frontend

PieceLocation
Pagesrc/pages/meetings/meetings-page.tsx
Form / detail / integrationsmeeting-form-dialog.tsx, meeting-detail-sheet.tsx, meeting-integrations-panel.tsx
APImeetingService in src/api/services.ts
Notificationssrc/notifications/modules/meetings.ts
E2Ee2e/tests/meetings/, npm run test:e2e:meetings

Provider options in the form are disabled until Integrations reports connected. Calendar projections link back to Meetings and are read-only on the Calendar sheet.

Permissions

RoleGrants
adminall including view_all, assign_host, manage_integrations
managerview/create/update/view_all/assign_host (no delete / manage_integrations)
staffview/create/update/delete (scoped); no view_all / manage_integrations

Registration (migrate-only)

  • 2026_07_22_000000_create_meetings_tables.php
  • 2026_07_22_000001_register_meetings_module.php
  • 2026_07_22_000002_add_meetings_permissions.php
  • 2026_07_22_000003_add_meetings_calendar_dependency.php

Also listed in CatalogSeeder for fresh/local/CI.

Tests

  • Pest: tests/Feature/Tenant/Meeting/*
  • Playwright: npm run test:e2e:meetings

Explicit non-goals (current)

  • Per-user personal Zoom/Google accounts
  • Multiple reminders per meeting
  • Changing Calendar ACLs so invitees see projected events (Meetings list remains invitee source of truth)

Official documentation for the EloSync SaaS Platform.