@fayz-ai/plugin-agenda 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +3 -3
  2. package/dist/bridges/create-financial-bridge.d.ts +1 -1
  3. package/dist/bridges/create-financial-bridge.d.ts.map +1 -1
  4. package/dist/bridges/financial-bridge.d.ts.map +1 -0
  5. package/dist/{chunk-Q2PAWBPR.js → chunk-ISLJXP7O.js} +7 -2
  6. package/dist/chunk-ISLJXP7O.js.map +1 -0
  7. package/dist/components/AppointmentModal.d.ts.map +1 -1
  8. package/dist/components/PaymentStatusBadge.d.ts +1 -1
  9. package/dist/components/PaymentStatusBadge.d.ts.map +1 -1
  10. package/dist/config.d.ts +2 -2
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/{AgendaContext.d.ts → context.d.ts} +1 -1
  13. package/dist/context.d.ts.map +1 -0
  14. package/dist/data/mock.d.ts.map +1 -1
  15. package/dist/data/realtime.test.d.ts +2 -0
  16. package/dist/data/realtime.test.d.ts.map +1 -0
  17. package/dist/data/registries.d.ts.map +1 -0
  18. package/dist/data/supabase.d.ts.map +1 -1
  19. package/dist/data/types.d.ts +7 -0
  20. package/dist/data/types.d.ts.map +1 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2515 -187
  24. package/dist/index.js.map +1 -1
  25. package/dist/integrations/google-calendar/conflict.d.ts +25 -0
  26. package/dist/integrations/google-calendar/conflict.d.ts.map +1 -0
  27. package/dist/integrations/google-calendar/connectorDef.d.ts +12 -0
  28. package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -1
  29. package/dist/integrations/google-calendar/credentials.d.ts +27 -0
  30. package/dist/integrations/google-calendar/credentials.d.ts.map +1 -0
  31. package/dist/integrations/google-calendar/data/supabase.d.ts +10 -2
  32. package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -1
  33. package/dist/integrations/google-calendar/data/tables.d.ts +2 -0
  34. package/dist/integrations/google-calendar/data/tables.d.ts.map +1 -1
  35. package/dist/integrations/google-calendar/health.d.ts +23 -0
  36. package/dist/integrations/google-calendar/health.d.ts.map +1 -0
  37. package/dist/integrations/google-calendar/health.test.d.ts +2 -0
  38. package/dist/integrations/google-calendar/health.test.d.ts.map +1 -0
  39. package/dist/integrations/google-calendar/index.d.ts +6 -0
  40. package/dist/integrations/google-calendar/index.d.ts.map +1 -1
  41. package/dist/integrations/google-calendar/migrations/index.d.ts +12 -0
  42. package/dist/integrations/google-calendar/migrations/index.d.ts.map +1 -0
  43. package/dist/integrations/google-calendar/panel-data.d.ts +26 -0
  44. package/dist/integrations/google-calendar/panel-data.d.ts.map +1 -0
  45. package/dist/integrations/google-calendar/panel-data.test.d.ts +2 -0
  46. package/dist/integrations/google-calendar/panel-data.test.d.ts.map +1 -0
  47. package/dist/integrations/google-calendar/types.d.ts +17 -0
  48. package/dist/integrations/google-calendar/types.d.ts.map +1 -1
  49. package/dist/lib/booking-events.d.ts +5 -0
  50. package/dist/lib/booking-events.d.ts.map +1 -0
  51. package/dist/lib/calendar-events.d.ts +8 -0
  52. package/dist/lib/calendar-events.d.ts.map +1 -0
  53. package/dist/lib/calendar-events.test.d.ts +2 -0
  54. package/dist/lib/calendar-events.test.d.ts.map +1 -0
  55. package/dist/lib/message-events.d.ts +12 -0
  56. package/dist/lib/message-events.d.ts.map +1 -0
  57. package/dist/locales/en.d.ts.map +1 -1
  58. package/dist/locales/pt-BR.d.ts.map +1 -1
  59. package/dist/public/{data.d.ts → data/index.d.ts} +4 -4
  60. package/dist/public/data/index.d.ts.map +1 -0
  61. package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
  62. package/dist/public/data/supabase.d.ts.map +1 -0
  63. package/dist/public/{hooks.d.ts → hooks/index.d.ts} +4 -4
  64. package/dist/public/hooks/index.d.ts.map +1 -0
  65. package/dist/public/index.d.ts +4 -4
  66. package/dist/public/index.d.ts.map +1 -1
  67. package/dist/public/index.js +3 -3
  68. package/dist/public/index.js.map +1 -1
  69. package/dist/public/{format.d.ts → lib/format.d.ts} +1 -1
  70. package/dist/public/lib/format.d.ts.map +1 -0
  71. package/dist/public/views/BookingWidget.d.ts.map +1 -0
  72. package/dist/public/{steps → views/steps}/ContactStep.d.ts +1 -1
  73. package/dist/public/views/steps/ContactStep.d.ts.map +1 -0
  74. package/dist/public/{steps → views/steps}/DateTimeStep.d.ts +3 -3
  75. package/dist/public/views/steps/DateTimeStep.d.ts.map +1 -0
  76. package/dist/public/{steps → views/steps}/OverviewStep.d.ts +1 -1
  77. package/dist/public/views/steps/OverviewStep.d.ts.map +1 -0
  78. package/dist/public/{steps → views/steps}/PaymentStep.d.ts +1 -1
  79. package/dist/public/views/steps/PaymentStep.d.ts.map +1 -0
  80. package/dist/store.d.ts +1 -1
  81. package/dist/store.d.ts.map +1 -1
  82. package/dist/styles/calendar-styles.d.ts.map +1 -0
  83. package/dist/types.d.ts +1 -1
  84. package/dist/types.d.ts.map +1 -1
  85. package/dist/{AgendaPage.d.ts → views/AgendaPage.d.ts} +3 -3
  86. package/dist/views/AgendaPage.d.ts.map +1 -0
  87. package/dist/views/CalendarView.d.ts.map +1 -1
  88. package/package.json +9 -11
  89. package/dist/AgendaContext.d.ts.map +0 -1
  90. package/dist/AgendaPage.d.ts.map +0 -1
  91. package/dist/calendar-styles.d.ts.map +0 -1
  92. package/dist/chunk-Q2PAWBPR.js.map +0 -1
  93. package/dist/financial-bridge.d.ts.map +0 -1
  94. package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts +0 -3
  95. package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts.map +0 -1
  96. package/dist/public/BookingWidget.d.ts.map +0 -1
  97. package/dist/public/data.d.ts.map +0 -1
  98. package/dist/public/data.supabase.d.ts.map +0 -1
  99. package/dist/public/format.d.ts.map +0 -1
  100. package/dist/public/hooks.d.ts.map +0 -1
  101. package/dist/public/steps/ContactStep.d.ts.map +0 -1
  102. package/dist/public/steps/DateTimeStep.d.ts.map +0 -1
  103. package/dist/public/steps/OverviewStep.d.ts.map +0 -1
  104. package/dist/public/steps/PaymentStep.d.ts.map +0 -1
  105. package/dist/registries.d.ts.map +0 -1
  106. package/src/AgendaContext.tsx +0 -11
  107. package/src/AgendaPage.tsx +0 -33
  108. package/src/bridges/create-financial-bridge.ts +0 -296
  109. package/src/calendar-styles.css +0 -245
  110. package/src/calendar-styles.ts +0 -285
  111. package/src/components/AgendaGeneralSettings.tsx +0 -86
  112. package/src/components/AgendaMobileList.tsx +0 -103
  113. package/src/components/AppointmentModal.tsx +0 -1050
  114. package/src/components/AppointmentPopover.tsx +0 -231
  115. package/src/components/BookingPaymentPanel.tsx +0 -394
  116. package/src/components/CalendarSidebar.tsx +0 -119
  117. package/src/components/CalendarToolbar.tsx +0 -86
  118. package/src/components/EventContextMenu.tsx +0 -125
  119. package/src/components/FloatingPanel.tsx +0 -123
  120. package/src/components/MiniCalendar.tsx +0 -120
  121. package/src/components/PaymentStatusBadge.tsx +0 -62
  122. package/src/components/StatusBadge.tsx +0 -23
  123. package/src/config.ts +0 -368
  124. package/src/data/index.ts +0 -4
  125. package/src/data/mock.ts +0 -344
  126. package/src/data/supabase.ts +0 -636
  127. package/src/data/types.ts +0 -37
  128. package/src/financial-bridge.ts +0 -96
  129. package/src/hooks/useAgendaSettings.ts +0 -17
  130. package/src/index.ts +0 -297
  131. package/src/integrations/google-calendar/GoogleCalendarSettings.tsx +0 -136
  132. package/src/integrations/google-calendar/components/CalendarColorDot.tsx +0 -23
  133. package/src/integrations/google-calendar/components/ChannelMappingList.tsx +0 -329
  134. package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +0 -215
  135. package/src/integrations/google-calendar/components/useAgendaLookups.ts +0 -100
  136. package/src/integrations/google-calendar/connector.ts +0 -20
  137. package/src/integrations/google-calendar/connectorDef.tsx +0 -185
  138. package/src/integrations/google-calendar/data/supabase.ts +0 -170
  139. package/src/integrations/google-calendar/data/tables.ts +0 -12
  140. package/src/integrations/google-calendar/index.ts +0 -45
  141. package/src/integrations/google-calendar/integration.test.ts +0 -199
  142. package/src/integrations/google-calendar/mapping.ts +0 -183
  143. package/src/integrations/google-calendar/migrations/001_google_calendar.sql +0 -97
  144. package/src/integrations/google-calendar/migrations/002_smart_sync.sql +0 -517
  145. package/src/integrations/google-calendar/migrations/003_plg_rename.sql +0 -273
  146. package/src/integrations/google-calendar/types.ts +0 -142
  147. package/src/lib/tenant.ts +0 -3
  148. package/src/locales/en.ts +0 -220
  149. package/src/locales/index.ts +0 -7
  150. package/src/locales/pt-BR.ts +0 -220
  151. package/src/migrations/000b_v_bookings_rename.sql +0 -21
  152. package/src/migrations/000c_dedupe_customer_phones.sql +0 -43
  153. package/src/migrations/001_public_booking.sql +0 -296
  154. package/src/migrations/002_v_bookings_compat.sql +0 -14
  155. package/src/migrations/003_public_client_kind.sql +0 -175
  156. package/src/migrations/004_public_booking_assignee.sql +0 -176
  157. package/src/migrations/005_agent_rpcs.sql +0 -168
  158. package/src/migrations/006_agent_rpc_localtime.sql +0 -181
  159. package/src/migrations/007_schedule_exception_precedence.sql +0 -290
  160. package/src/migrations/008_agent_rpc_exception_precedence.sql +0 -172
  161. package/src/migrations/009_public_booking_location.sql +0 -162
  162. package/src/migrations/010_public_service_curation.sql +0 -31
  163. package/src/migrations/011_default_assignee.sql +0 -167
  164. package/src/migrations/012_drop_legacy_booking_overload.sql +0 -29
  165. package/src/migrations/013_normalize_v_appointments.sql +0 -199
  166. package/src/migrations/014_agent_rpc_update_appointment.sql +0 -199
  167. package/src/migrations/index.ts +0 -2377
  168. package/src/public/BookingWidget.tsx +0 -324
  169. package/src/public/components/BrandHeader.tsx +0 -27
  170. package/src/public/components/ServiceCard.tsx +0 -39
  171. package/src/public/components/SidebarExtras.tsx +0 -30
  172. package/src/public/components/StepRailItem.tsx +0 -27
  173. package/src/public/components/SuccessPanel.tsx +0 -27
  174. package/src/public/components/SummaryRail.tsx +0 -90
  175. package/src/public/components/defaults.ts +0 -14
  176. package/src/public/context.tsx +0 -103
  177. package/src/public/createPublicBookingPlugin.ts +0 -281
  178. package/src/public/data.supabase.ts +0 -97
  179. package/src/public/data.ts +0 -105
  180. package/src/public/format.ts +0 -96
  181. package/src/public/hooks.ts +0 -110
  182. package/src/public/index.ts +0 -69
  183. package/src/public/steps/ContactStep.tsx +0 -184
  184. package/src/public/steps/DateTimeStep.tsx +0 -138
  185. package/src/public/steps/OverviewStep.tsx +0 -81
  186. package/src/public/steps/PaymentStep.tsx +0 -180
  187. package/src/public/types.ts +0 -250
  188. package/src/registries.ts +0 -35
  189. package/src/store.ts +0 -311
  190. package/src/types.ts +0 -273
  191. package/src/views/CalendarView.tsx +0 -1112
  192. package/src/views/ConfirmationsView.tsx +0 -155
  193. /package/dist/{financial-bridge.d.ts → bridges/financial-bridge.d.ts} +0 -0
  194. /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
  195. /package/dist/public/{BookingWidget.d.ts → views/BookingWidget.d.ts} +0 -0
  196. /package/dist/{calendar-styles.d.ts → styles/calendar-styles.d.ts} +0 -0
@@ -1,183 +0,0 @@
1
- // Booking ↔ Google Calendar event mapping — the SINGLE SOURCE OF TRUTH.
2
- //
3
- // These helpers are pure and Deno-friendly (only `import type`, no runtime
4
- // node/react/https imports), so the Supabase edge function imports this exact
5
- // file via a relative path (`../../src/integrations/google-calendar/mapping.ts`)
6
- // instead of keeping a divergent mirror. `import type` is elided by the Deno /
7
- // esbuild bundler, so the plugin `types.ts`/`CalendarBooking` type imports never
8
- // become runtime dependencies of the function bundle.
9
- //
10
- // Two callers, two input shapes:
11
- // • the SDK/UI works with the rich `CalendarBooking` (camelCase, joined) — see
12
- // bookingToEvent / eventToBookingPatch;
13
- // • the edge function + RPCs work with the raw `AppointmentRow` (snake_case DB
14
- // columns) — see appointmentRowToEvent / eventToAppointmentPatch.
15
- // Field decisions (title, all-day, cancel) are made once, here.
16
- import type { CalendarBooking } from '../../types'
17
- import type { GCalEvent, AppointmentRow, CalendarChannel, CalendarChannelRow } from './types'
18
-
19
- export interface GCalEventInput {
20
- summary: string
21
- description?: string
22
- // Timed events use dateTime; all-day events use date (YYYY-MM-DD).
23
- start: { dateTime?: string; date?: string; timeZone?: string }
24
- end: { dateTime?: string; date?: string; timeZone?: string }
25
- }
26
-
27
- /** Normalized time window read off a Google event, all-day aware. */
28
- export interface EventTimes {
29
- startsAt: string
30
- endsAt: string
31
- allDay: boolean
32
- }
33
-
34
- // --- Booking (rich SDK shape) → event -------------------------------------
35
-
36
- /** Build the event title from the booking's services + client. */
37
- export function bookingSummary(b: Pick<CalendarBooking, 'services' | 'clientName'>): string {
38
- const services = (b.services ?? []).map((s) => s.name).filter(Boolean).join(', ')
39
- const who = b.clientName ?? ''
40
- if (services && who) return `${services} — ${who}`
41
- return services || who || 'Appointment'
42
- }
43
-
44
- /** Map a Fayz booking to a Google Calendar event input. Returns null when the booking has no end. */
45
- export function bookingToEvent(b: CalendarBooking, timeZone?: string): GCalEventInput | null {
46
- if (!b.startsAt || !b.endsAt) return null
47
- const notes = [b.notes, b.professionalName ? `Pro: ${b.professionalName}` : '', b.locationName ?? '']
48
- .filter(Boolean).join('\n')
49
- return {
50
- summary: bookingSummary(b),
51
- description: notes || undefined,
52
- start: { dateTime: b.startsAt, timeZone },
53
- end: { dateTime: b.endsAt, timeZone },
54
- }
55
- }
56
-
57
- /** Map an inbound Google event change to a partial booking update (reschedule/cancel), all-day aware. */
58
- export function eventToBookingPatch(ev: GCalEvent): { startsAt?: string; endsAt?: string; status?: 'cancelled' } {
59
- if (ev.status === 'cancelled') return { status: 'cancelled' }
60
- const t = eventTimes(ev)
61
- return { startsAt: t.startsAt, endsAt: t.endsAt }
62
- }
63
-
64
- // --- Appointment row (raw DB shape) → event -------------------------------
65
-
66
- /**
67
- * Build the event title from an appointments row's denormalized metadata.
68
- * The raw row has no joined services/client, so we read the names the app
69
- * stamps onto metadata (serviceNames, contactName/clientName).
70
- */
71
- export function appointmentRowSummary(row: AppointmentRow): string {
72
- const m = row.metadata ?? {}
73
- const services = (m.serviceNames as string) ?? ''
74
- const who = (m.contactName as string) ?? (m.clientName as string) ?? ''
75
- if (services && who) return `${services} — ${who}`
76
- return services || who || 'Appointment'
77
- }
78
-
79
- /** Map a raw appointments row to a Google event input (outbound push from the edge function). */
80
- export function appointmentRowToEvent(row: AppointmentRow): GCalEventInput {
81
- return {
82
- summary: appointmentRowSummary(row),
83
- description: row.notes ?? undefined,
84
- start: { dateTime: row.starts_at },
85
- end: { dateTime: row.ends_at ?? row.starts_at },
86
- }
87
- }
88
-
89
- /**
90
- * Map an inbound Google event to a DB-friendly appointment patch (snake_case
91
- * times), all-day aware, cancellation included. Consumed by the edge function
92
- * when calling gcal_apply_event_patch.
93
- */
94
- export function eventToAppointmentPatch(ev: GCalEvent): {
95
- starts_at?: string
96
- ends_at?: string
97
- summary?: string
98
- allDay?: boolean
99
- cancelled: boolean
100
- } {
101
- if (ev.status === 'cancelled') return { cancelled: true }
102
- const t = eventTimes(ev)
103
- return { starts_at: t.startsAt, ends_at: t.endsAt, summary: ev.summary, allDay: t.allDay, cancelled: false }
104
- }
105
-
106
- // --- Shared time normalization (all-day aware) ----------------------------
107
-
108
- /**
109
- * Read a normalized {startsAt, endsAt, allDay} window off a Google event.
110
- * All-day events carry `start.date`/`end.date` (YYYY-MM-DD, end exclusive) and
111
- * no dateTime; timed events carry dateTime. Falls back start→end when one side
112
- * is missing so callers always get a usable pair.
113
- */
114
- export function eventTimes(ev: GCalEvent): EventTimes {
115
- const allDay = !ev.start?.dateTime && !!ev.start?.date
116
- const startsAt = ev.start?.dateTime ?? ev.start?.date ?? ''
117
- const endsAt = ev.end?.dateTime ?? ev.end?.date ?? startsAt
118
- return { startsAt, endsAt, allDay }
119
- }
120
-
121
- // --- Channel resolution (outbound destination priority) -------------------
122
-
123
- /** Convert a raw plg_calendar_channels row (snake_case) to the camelCase CalendarChannel. */
124
- export function channelRowToChannel(r: CalendarChannelRow): CalendarChannel {
125
- return {
126
- id: r.id,
127
- integrationId: r.integration_id,
128
- tenantId: r.tenant_id,
129
- googleCalendarId: r.google_calendar_id,
130
- summary: r.summary ?? null,
131
- color: r.color ?? null,
132
- direction: r.direction,
133
- targetKind: r.target_kind ?? null,
134
- targetId: r.target_id ?? null,
135
- importMode: r.import_mode ?? 'appointment',
136
- syncToken: r.sync_token ?? null,
137
- channelId: r.channel_id ?? null,
138
- resourceId: r.resource_id ?? null,
139
- channelExpiresAt: r.channel_expires_at ?? null,
140
- isActive: r.is_active ?? true,
141
- createdAt: r.created_at ?? '',
142
- updatedAt: r.updated_at ?? '',
143
- }
144
- }
145
-
146
- export interface ChannelMatchInput {
147
- assigneeId?: string | null
148
- serviceIds?: string[]
149
- locationId?: string | null
150
- }
151
-
152
- /**
153
- * Pick the outbound destination channel for a booking, by priority:
154
- * 1. a channel targeting the booking's assignee,
155
- * 2. else a channel targeting one of the booking's services,
156
- * 3. else a channel targeting the booking's location,
157
- * 4. else the whole-agenda channel (targetKind === null).
158
- * Only active outbound/bidirectional channels are eligible. Returns null when
159
- * nothing matches (caller falls back to the integration's default calendar).
160
- */
161
- export function resolveTargetChannel(
162
- channels: CalendarChannel[],
163
- booking: ChannelMatchInput,
164
- ): CalendarChannel | null {
165
- const eligible = channels.filter(
166
- (c) => c.isActive && (c.direction === 'outbound' || c.direction === 'bidirectional'),
167
- )
168
- const serviceIds = booking.serviceIds ?? []
169
-
170
- if (booking.assigneeId) {
171
- const byAssignee = eligible.find((c) => c.targetKind === 'assignee' && c.targetId === booking.assigneeId)
172
- if (byAssignee) return byAssignee
173
- }
174
- if (serviceIds.length) {
175
- const byService = eligible.find((c) => c.targetKind === 'service' && !!c.targetId && serviceIds.includes(c.targetId))
176
- if (byService) return byService
177
- }
178
- if (booking.locationId) {
179
- const byLocation = eligible.find((c) => c.targetKind === 'location' && c.targetId === booking.locationId)
180
- if (byLocation) return byLocation
181
- }
182
- return eligible.find((c) => c.targetKind === null) ?? null
183
- }
@@ -1,97 +0,0 @@
1
- -- ---------------------------------------------------------------------------
2
- -- Google Calendar integration (bidirectional) — connection + audit + triggers
3
- -- ---------------------------------------------------------------------------
4
- -- Outbound (Fayz booking → Google event) fires from a trigger on public.appointments
5
- -- via pg_net to the google-calendar-sync edge function (push_event). Inbound
6
- -- (Google → booking) runs on a pg_cron schedule (pull_events) and/or a Google
7
- -- watch-channel webhook. The booking↔event link is stored on
8
- -- public.appointments.metadata.googleCalendarEventId (no schema change there).
9
-
10
- -- Plugin-owned tables carry the platform `plg_<plugin>_` prefix (DATA-MODEL.md
11
- -- Ring 1). Legacy pools provisioned under the old `calendar_*` names are migrated
12
- -- by 003_plg_rename.sql; fresh installs are born with the prefixed names here.
13
- CREATE TABLE IF NOT EXISTS public.plg_calendar_integrations (
14
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
15
- tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
16
- provider text NOT NULL DEFAULT 'google',
17
- oauth_refresh_token text,
18
- oauth_access_token text,
19
- token_expires_at timestamptz,
20
- calendar_id text NOT NULL DEFAULT 'primary',
21
- -- Incremental pull cursor + Google watch channel bookkeeping.
22
- sync_token text,
23
- channel_id text,
24
- channel_resource_id text,
25
- channel_expires_at timestamptz,
26
- active boolean NOT NULL DEFAULT true,
27
- last_sync_at timestamptz,
28
- created_at timestamptz NOT NULL DEFAULT now(),
29
- updated_at timestamptz NOT NULL DEFAULT now(),
30
- UNIQUE (tenant_id, provider)
31
- );
32
- ALTER TABLE public.plg_calendar_integrations ENABLE ROW LEVEL SECURITY;
33
- CREATE INDEX IF NOT EXISTS idx_plg_calendar_integrations_tenant ON public.plg_calendar_integrations(tenant_id);
34
-
35
- CREATE TABLE IF NOT EXISTS public.plg_calendar_sync_log (
36
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
37
- tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
38
- direction text NOT NULL, -- inbound | outbound
39
- trigger text, -- on-write | scheduled | webhook | manual
40
- status text NOT NULL DEFAULT 'success', -- success | partial | error
41
- fetched integer NOT NULL DEFAULT 0,
42
- written integer NOT NULL DEFAULT 0,
43
- error text,
44
- created_at timestamptz NOT NULL DEFAULT now()
45
- );
46
- ALTER TABLE public.plg_calendar_sync_log ENABLE ROW LEVEL SECURITY;
47
-
48
- -- RLS: tenant isolation.
49
- DO $$
50
- DECLARE t text;
51
- BEGIN
52
- FOR t IN SELECT unnest(ARRAY['plg_calendar_integrations','plg_calendar_sync_log'])
53
- LOOP
54
- EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO authenticated', t);
55
- IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_rls') THEN
56
- EXECUTE format('CREATE POLICY %I ON public.%I FOR ALL TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids())) WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_rls', t);
57
- END IF;
58
- END LOOP;
59
- END $$;
60
-
61
- -- ---------------------------------------------------------------------------
62
- -- Outbound trigger: booking change → edge function (push_event).
63
- -- Requires pg_net + a configured function URL. Set once per project:
64
- -- ALTER DATABASE postgres SET app.gcal_sync_url = 'https://<ref>.functions.supabase.co/google-calendar-sync';
65
- -- ALTER DATABASE postgres SET app.gcal_service_key = '<service-role-key>';
66
- -- Safe to skip in environments without pg_net — outbound also works via the
67
- -- client-side withGoogleCalendarSync() decorator.
68
- -- ---------------------------------------------------------------------------
69
- CREATE OR REPLACE FUNCTION public.fn_gcal_push_booking() RETURNS trigger
70
- LANGUAGE plpgsql SECURITY DEFINER AS $$
71
- DECLARE
72
- url text := current_setting('app.gcal_sync_url', true);
73
- key text := current_setting('app.gcal_service_key', true);
74
- rec record := COALESCE(NEW, OLD);
75
- op text := lower(TG_OP);
76
- BEGIN
77
- IF url IS NULL OR url = '' THEN RETURN COALESCE(NEW, OLD); END IF;
78
- PERFORM net.http_post(
79
- url := url,
80
- headers := jsonb_build_object('Content-Type', 'application/json', 'Authorization', 'Bearer ' || COALESCE(key, '')),
81
- body := jsonb_build_object('action', 'push_event', 'op', op, 'bookingId', rec.id, 'tenantId', rec.tenant_id)
82
- );
83
- RETURN COALESCE(NEW, OLD);
84
- END $$;
85
-
86
- DROP TRIGGER IF EXISTS trg_gcal_push ON public.appointments;
87
- CREATE TRIGGER trg_gcal_push
88
- AFTER INSERT OR UPDATE OR DELETE ON public.appointments
89
- FOR EACH ROW EXECUTE FUNCTION public.fn_gcal_push_booking();
90
-
91
- -- Inbound schedule (optional): pull external changes every 10 minutes.
92
- -- SELECT cron.schedule('gcal-pull', '*/10 * * * *', $$
93
- -- SELECT net.http_post(
94
- -- url := current_setting('app.gcal_sync_url'),
95
- -- headers := jsonb_build_object('Content-Type','application/json','Authorization','Bearer '||current_setting('app.gcal_service_key')),
96
- -- body := jsonb_build_object('action','pull_events'));
97
- -- $$);