Skip to content

Log Background Sync #3135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
-- Add workspace_calendar_sync_log table to track calendar sync operations

-- Create workspace_calendar_sync_log table
CREATE TABLE "public"."workspace_calendar_sync_log" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"ws_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
"access_token" text NOT NULL,
"refresh_token" text NOT NULL,
"sync_started_at" timestamp with time zone NOT NULL,
"sync_ended_at" timestamp with time zone,
"status" text NOT NULL,
"error_message" text,
"event_snapshot_before" jsonb NOT NULL,
"upserted_events" jsonb,
"deleted_events" jsonb,
"triggered_by" text NOT NULL,
"created_at" timestamp with time zone NOT NULL DEFAULT now()
);

-- Enable Row Level Security
ALTER TABLE "public"."workspace_calendar_sync_log" ENABLE ROW LEVEL SECURITY;

-- Create primary key
CREATE UNIQUE INDEX workspace_calendar_sync_log_pkey ON public.workspace_calendar_sync_log USING btree (id);
ALTER TABLE "public"."workspace_calendar_sync_log" ADD CONSTRAINT "workspace_calendar_sync_log_pkey" PRIMARY KEY using index "workspace_calendar_sync_log_pkey";

-- Create indexes for better query performance
CREATE INDEX workspace_calendar_sync_log_workspace_id_idx ON public.workspace_calendar_sync_log USING btree (ws_id);
CREATE INDEX workspace_calendar_sync_log_status_idx ON public.workspace_calendar_sync_log USING btree (status);
CREATE INDEX workspace_calendar_sync_log_sync_started_at_idx ON public.workspace_calendar_sync_log USING btree (sync_started_at);

-- RLS Policies
-- Users can see sync logs for workspaces they are members of
CREATE POLICY "Users can view sync logs for their workspaces"
ON public.workspace_calendar_sync_log
FOR SELECT
TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.workspace_members wm
WHERE wm.ws_id = ws_id
AND wm.user_id = auth.uid()
)
);

-- Users can insert sync logs for workspaces they are members of
CREATE POLICY "Users can insert sync logs for their workspaces"
ON public.workspace_calendar_sync_log
FOR INSERT
TO authenticated
WITH CHECK (
EXISTS (
SELECT 1 FROM public.workspace_members wm
WHERE wm.ws_id = ws_id
AND wm.user_id = auth.uid()
)
);

-- Users can update sync logs for workspaces they are members of
CREATE POLICY "Users can update sync logs for their workspaces"
ON public.workspace_calendar_sync_log
FOR UPDATE
TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.workspace_members wm
WHERE wm.ws_id = ws_id
AND wm.user_id = auth.uid()
)
)
WITH CHECK (
EXISTS (
SELECT 1 FROM public.workspace_members wm
WHERE wm.ws_id = ws_id
AND wm.user_id = auth.uid()
)
);

-- Add check constraint for status values
ALTER TABLE "public"."workspace_calendar_sync_log"
ADD CONSTRAINT "workspace_calendar_sync_log_status_check"
CHECK (status IN ('success', 'failed', 'in_progress', 'cancelled', 'partial_success'));

-- Add check constraint for triggered_by values
ALTER TABLE "public"."workspace_calendar_sync_log"
ADD CONSTRAINT "workspace_calendar_sync_log_triggered_by_check"
CHECK (triggered_by IN ('active_sync', 'trigger_dot_dev', 'manual'));

-- Add check constraint to ensure sync_ended_at is after sync_started_at when both are present
ALTER TABLE "public"."workspace_calendar_sync_log"
ADD CONSTRAINT "workspace_calendar_sync_log_timestamps_check"
CHECK (sync_ended_at IS NULL OR sync_ended_at >= sync_started_at);
128 changes: 127 additions & 1 deletion packages/trigger/google-calendar-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,80 @@ export const syncGoogleCalendarEvents = async () => {
tokens.map((token) => token.ws_id)
);

const endSync = async (
ws_id: string,
access_token: string,
refresh_token: string,
syncStartedAt: string,
syncEndedAt: string,
status: string,
errorMessage: string,
eventSnapshotBefore: WorkspaceCalendarEvent[],
upsertedEvents: WorkspaceCalendarEvent[],
deletedEvents: WorkspaceCalendarEvent[]
) => {
await supabase.from('workspace_calendar_sync_log').insert({
ws_id,
access_token,
refresh_token,
sync_started_at: syncStartedAt,
sync_ended_at: syncEndedAt,
status: status,
error_message: errorMessage,
event_snapshot_before: eventSnapshotBefore,
upserted_events: upsertedEvents,
deleted_events: deletedEvents,
triggered_by: 'trigger_dot_dev',
});
};

for (const token of tokens || []) {
const syncStartedAt = dayjs().toISOString();
const { data: eventsBeforeSync, error: errorEventsBeforeSync } =
await supabase
.from('workspace_calendar_events')
.select('*')
.eq('ws_id', token.ws_id)
.not('google_event_id', 'is', null);
if (errorEventsBeforeSync) {
console.error(
'Error fetching events before sync:',
errorEventsBeforeSync
);
endSync(
token.ws_id,
token.access_token,
token.refresh_token,
syncStartedAt,
dayjs().toISOString(),
'failed',
'Error fetching events before sync: ' + errorEventsBeforeSync.message,
[],
[],
[]
);
continue;
}
const { ws_id, access_token, refresh_token } = token;
if (!access_token) {
console.error('No Google access token found for wsIds:', {
ws_id,
hasAccessToken: !!access_token,
hasRefreshToken: !!refresh_token,
});
endSync(
token.ws_id,
token.access_token,
token.refresh_token,
syncStartedAt,
dayjs().toISOString(),
'failed',
'No Google access token found for wsIds',
eventsBeforeSync || [],
[],
[]
);
continue;
}

try {
Expand Down Expand Up @@ -115,7 +181,18 @@ export const syncGoogleCalendarEvents = async () => {
});
if (error) {
console.error('Error upserting events:', error);
continue;
endSync(
ws_id,
access_token,
refresh_token,
syncStartedAt,
dayjs().toISOString(),
'failed',
'Error upserting events: ' + error.message,
eventsBeforeSync || [],
[],
[]
);
}
console.log(
'Upserted events for wsId:',
Expand All @@ -134,6 +211,18 @@ export const syncGoogleCalendarEvents = async () => {

if (dbError) {
console.error('Error fetching events after upsert:', dbError);
endSync(
ws_id,
access_token,
refresh_token,
syncStartedAt,
dayjs().toISOString(),
'failed',
'Error fetching events after upsert: ' + dbError.message,
eventsBeforeSync || [],
formattedEvents as WorkspaceCalendarEvent[],
[]
);
continue;
}

Expand All @@ -158,6 +247,18 @@ export const syncGoogleCalendarEvents = async () => {

if (deleteError) {
console.error('Error deleting events:', deleteError);
endSync(
ws_id,
access_token,
refresh_token,
syncStartedAt,
dayjs().toISOString(),
'failed',
'Error deleting events: ' + deleteError.message,
eventsBeforeSync || [],
formattedEvents as WorkspaceCalendarEvent[],
[]
);
continue;
}

Expand All @@ -167,10 +268,35 @@ export const syncGoogleCalendarEvents = async () => {
eventsToDelete.map((e) => e.title)
);

endSync(
ws_id,
access_token,
refresh_token,
syncStartedAt,
dayjs().toISOString(),
'success',
'',
eventsBeforeSync || [],
formattedEvents as WorkspaceCalendarEvent[],
eventsToDelete as WorkspaceCalendarEvent[]
);

// Update lastUpsert timestamp after successful upsert
await updateLastUpsert(ws_id, supabase);
} catch (error) {
console.error('Error fetching Google Calendar events:', error);
endSync(
ws_id,
access_token,
refresh_token,
syncStartedAt,
dayjs().toISOString(),
'failed',
'Error fetching Google Calendar events: ' + error,
eventsBeforeSync || [],
[],
[]
);
}
}
} catch (error) {
Expand Down
Loading