diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index bde554eaad..3456e8a8d8 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -723,33 +723,41 @@ export default class GoogleCalendarService implements Calendar { log.warn("GOOGLE_WEBHOOK_TOKEN is not set, skipping watching calendar"); return; } - const calendar = await this.authedCalendar(); - const res = await calendar.events.watch({ - // Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the "primary" keyword. - calendarId, - requestBody: { - // A UUID or similar unique string that identifies this channel. - id: uuid(), - type: "web_hook", - address: GOOGLE_WEBHOOK_URL, - token: process.env.GOOGLE_WEBHOOK_TOKEN, - params: { - // The time-to-live in seconds for the notification channel. Default is 604800 seconds. - ttl: `${Math.round(ONE_MONTH_IN_MS / 1000)}`, + try { + const calendar = await this.authedCalendar(); + const res = await calendar.events.watch({ + // Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the "primary" keyword. + calendarId, + requestBody: { + // A UUID or similar unique string that identifies this channel. + id: uuid(), + type: "web_hook", + address: GOOGLE_WEBHOOK_URL, + token: process.env.GOOGLE_WEBHOOK_TOKEN, + params: { + // The time-to-live in seconds for the notification channel. Default is 604800 seconds. + ttl: `${Math.round(ONE_MONTH_IN_MS / 1000)}`, + }, }, - }, - }); - const response = res.data; - await this.upsertSelectedCalendar({ - externalId: calendarId, - googleChannelId: response?.id, - googleChannelKind: response?.kind, - googleChannelResourceId: response?.resourceId, - googleChannelResourceUri: response?.resourceUri, - googleChannelExpiration: response?.expiration, - }); + }); + const response = res.data; + await this.upsertSelectedCalendar({ + externalId: calendarId, + googleChannelId: response?.id, + googleChannelKind: response?.kind, + googleChannelResourceId: response?.resourceId, + googleChannelResourceUri: response?.resourceUri, + googleChannelExpiration: response?.expiration, + }); - return res.data; + return res.data; + } catch (error) { + this.log.error(`Failed to watch calendar ${calendarId}`, error); + await this.upsertSelectedCalendar({ + externalId: calendarId, + error: error instanceof Error ? error.message : "Unknown error", + }); + } } async unwatchCalendar({ calendarId }: { calendarId: string }) { const credentialId = this.credential.id; diff --git a/packages/lib/server/repository/selectedCalendar.ts b/packages/lib/server/repository/selectedCalendar.ts index ed0e00ed20..a208752542 100644 --- a/packages/lib/server/repository/selectedCalendar.ts +++ b/packages/lib/server/repository/selectedCalendar.ts @@ -47,6 +47,8 @@ export class SelectedCalendarRepository { }, // RN we only support google calendar subscriptions for now integration: "google_calendar", + // We skip retrying calendars that have errored + error: null, OR: [ // Either is a calendar pending to be watched { googleChannelExpiration: null }, diff --git a/packages/prisma/migrations/20241218164539_add_selected_calendar_error/migration.sql b/packages/prisma/migrations/20241218164539_add_selected_calendar_error/migration.sql new file mode 100644 index 0000000000..d665820985 --- /dev/null +++ b/packages/prisma/migrations/20241218164539_add_selected_calendar_error/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "SelectedCalendar" ADD COLUMN "error" TEXT; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 1f0fde1ab9..68bf38d704 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -725,6 +725,7 @@ model SelectedCalendar { googleChannelExpiration String? domainWideDelegationCredential DomainWideDelegation? @relation(fields: [domainWideDelegationCredentialId], references: [id], onDelete: Cascade) domainWideDelegationCredentialId String? + error String? @@id([userId, integration, externalId]) @@index([userId])