@fayz-ai/plugin-agenda 0.9.0 → 0.9.1
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.
- package/README.md +3 -3
- package/dist/bridges/create-financial-bridge.d.ts +1 -1
- package/dist/bridges/create-financial-bridge.d.ts.map +1 -1
- package/dist/bridges/financial-bridge.d.ts.map +1 -0
- package/dist/{chunk-Q2PAWBPR.js → chunk-76ZOHRBM.js} +2 -2
- package/dist/{chunk-Q2PAWBPR.js.map → chunk-76ZOHRBM.js.map} +1 -1
- package/dist/components/PaymentStatusBadge.d.ts +1 -1
- package/dist/components/PaymentStatusBadge.d.ts.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/{AgendaContext.d.ts → context.d.ts} +1 -1
- package/dist/context.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +170 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/message-events.d.ts +12 -0
- package/dist/lib/message-events.d.ts.map +1 -0
- package/dist/public/{data.d.ts → data/index.d.ts} +4 -4
- package/dist/public/data/index.d.ts.map +1 -0
- package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
- package/dist/public/data/supabase.d.ts.map +1 -0
- package/dist/public/{hooks.d.ts → hooks/index.d.ts} +4 -4
- package/dist/public/hooks/index.d.ts.map +1 -0
- package/dist/public/index.d.ts +4 -4
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +3 -3
- package/dist/public/index.js.map +1 -1
- package/dist/public/{format.d.ts → lib/format.d.ts} +1 -1
- package/dist/public/lib/format.d.ts.map +1 -0
- package/dist/public/views/BookingWidget.d.ts.map +1 -0
- package/dist/public/{steps → views/steps}/ContactStep.d.ts +1 -1
- package/dist/public/views/steps/ContactStep.d.ts.map +1 -0
- package/dist/public/{steps → views/steps}/DateTimeStep.d.ts +3 -3
- package/dist/public/views/steps/DateTimeStep.d.ts.map +1 -0
- package/dist/public/{steps → views/steps}/OverviewStep.d.ts +1 -1
- package/dist/public/views/steps/OverviewStep.d.ts.map +1 -0
- package/dist/public/{steps → views/steps}/PaymentStep.d.ts +1 -1
- package/dist/public/views/steps/PaymentStep.d.ts.map +1 -0
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/styles/calendar-styles.d.ts.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/{AgendaPage.d.ts → views/AgendaPage.d.ts} +3 -3
- package/dist/views/AgendaPage.d.ts.map +1 -0
- package/package.json +6 -9
- package/dist/AgendaContext.d.ts.map +0 -1
- package/dist/AgendaPage.d.ts.map +0 -1
- package/dist/calendar-styles.d.ts.map +0 -1
- package/dist/financial-bridge.d.ts.map +0 -1
- package/dist/public/BookingWidget.d.ts.map +0 -1
- package/dist/public/data.d.ts.map +0 -1
- package/dist/public/data.supabase.d.ts.map +0 -1
- package/dist/public/format.d.ts.map +0 -1
- package/dist/public/hooks.d.ts.map +0 -1
- package/dist/public/steps/ContactStep.d.ts.map +0 -1
- package/dist/public/steps/DateTimeStep.d.ts.map +0 -1
- package/dist/public/steps/OverviewStep.d.ts.map +0 -1
- package/dist/public/steps/PaymentStep.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/AgendaContext.tsx +0 -11
- package/src/AgendaPage.tsx +0 -33
- package/src/bridges/create-financial-bridge.ts +0 -296
- package/src/calendar-styles.css +0 -245
- package/src/calendar-styles.ts +0 -285
- package/src/components/AgendaGeneralSettings.tsx +0 -86
- package/src/components/AgendaMobileList.tsx +0 -103
- package/src/components/AppointmentModal.tsx +0 -1050
- package/src/components/AppointmentPopover.tsx +0 -231
- package/src/components/BookingPaymentPanel.tsx +0 -394
- package/src/components/CalendarSidebar.tsx +0 -119
- package/src/components/CalendarToolbar.tsx +0 -86
- package/src/components/EventContextMenu.tsx +0 -125
- package/src/components/FloatingPanel.tsx +0 -123
- package/src/components/MiniCalendar.tsx +0 -120
- package/src/components/PaymentStatusBadge.tsx +0 -62
- package/src/components/StatusBadge.tsx +0 -23
- package/src/config.ts +0 -368
- package/src/data/index.ts +0 -4
- package/src/data/mock.ts +0 -344
- package/src/data/supabase.ts +0 -636
- package/src/data/types.ts +0 -37
- package/src/financial-bridge.ts +0 -96
- package/src/hooks/useAgendaSettings.ts +0 -17
- package/src/index.ts +0 -297
- package/src/integrations/google-calendar/GoogleCalendarSettings.tsx +0 -136
- package/src/integrations/google-calendar/components/CalendarColorDot.tsx +0 -23
- package/src/integrations/google-calendar/components/ChannelMappingList.tsx +0 -329
- package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +0 -215
- package/src/integrations/google-calendar/components/useAgendaLookups.ts +0 -100
- package/src/integrations/google-calendar/connector.ts +0 -20
- package/src/integrations/google-calendar/connectorDef.tsx +0 -185
- package/src/integrations/google-calendar/data/supabase.ts +0 -170
- package/src/integrations/google-calendar/data/tables.ts +0 -12
- package/src/integrations/google-calendar/index.ts +0 -45
- package/src/integrations/google-calendar/integration.test.ts +0 -199
- package/src/integrations/google-calendar/mapping.ts +0 -183
- package/src/integrations/google-calendar/migrations/001_google_calendar.sql +0 -97
- package/src/integrations/google-calendar/migrations/002_smart_sync.sql +0 -517
- package/src/integrations/google-calendar/migrations/003_plg_rename.sql +0 -273
- package/src/integrations/google-calendar/types.ts +0 -142
- package/src/lib/tenant.ts +0 -3
- package/src/locales/en.ts +0 -220
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -220
- package/src/migrations/000b_v_bookings_rename.sql +0 -21
- package/src/migrations/000c_dedupe_customer_phones.sql +0 -43
- package/src/migrations/001_public_booking.sql +0 -296
- package/src/migrations/002_v_bookings_compat.sql +0 -14
- package/src/migrations/003_public_client_kind.sql +0 -175
- package/src/migrations/004_public_booking_assignee.sql +0 -176
- package/src/migrations/005_agent_rpcs.sql +0 -168
- package/src/migrations/006_agent_rpc_localtime.sql +0 -181
- package/src/migrations/007_schedule_exception_precedence.sql +0 -290
- package/src/migrations/008_agent_rpc_exception_precedence.sql +0 -172
- package/src/migrations/009_public_booking_location.sql +0 -162
- package/src/migrations/010_public_service_curation.sql +0 -31
- package/src/migrations/011_default_assignee.sql +0 -167
- package/src/migrations/012_drop_legacy_booking_overload.sql +0 -29
- package/src/migrations/013_normalize_v_appointments.sql +0 -199
- package/src/migrations/014_agent_rpc_update_appointment.sql +0 -199
- package/src/migrations/index.ts +0 -2377
- package/src/public/BookingWidget.tsx +0 -324
- package/src/public/components/BrandHeader.tsx +0 -27
- package/src/public/components/ServiceCard.tsx +0 -39
- package/src/public/components/SidebarExtras.tsx +0 -30
- package/src/public/components/StepRailItem.tsx +0 -27
- package/src/public/components/SuccessPanel.tsx +0 -27
- package/src/public/components/SummaryRail.tsx +0 -90
- package/src/public/components/defaults.ts +0 -14
- package/src/public/context.tsx +0 -103
- package/src/public/createPublicBookingPlugin.ts +0 -281
- package/src/public/data.supabase.ts +0 -97
- package/src/public/data.ts +0 -105
- package/src/public/format.ts +0 -96
- package/src/public/hooks.ts +0 -110
- package/src/public/index.ts +0 -69
- package/src/public/steps/ContactStep.tsx +0 -184
- package/src/public/steps/DateTimeStep.tsx +0 -138
- package/src/public/steps/OverviewStep.tsx +0 -81
- package/src/public/steps/PaymentStep.tsx +0 -180
- package/src/public/types.ts +0 -250
- package/src/registries.ts +0 -35
- package/src/store.ts +0 -311
- package/src/types.ts +0 -273
- package/src/views/CalendarView.tsx +0 -1112
- package/src/views/ConfirmationsView.tsx +0 -155
- /package/dist/{financial-bridge.d.ts → bridges/financial-bridge.d.ts} +0 -0
- /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
- /package/dist/public/{BookingWidget.d.ts → views/BookingWidget.d.ts} +0 -0
- /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
|
-
-- $$);
|