diff --git a/packages/backend/src/common/errors/sync/sync.errors.ts b/packages/backend/src/common/errors/sync/sync.errors.ts index 425d059c6..520d980df 100644 --- a/packages/backend/src/common/errors/sync/sync.errors.ts +++ b/packages/backend/src/common/errors/sync/sync.errors.ts @@ -3,6 +3,7 @@ import { ErrorMetadata } from "@backend/common/types/error.types"; interface SyncErrors { AccessRevoked: ErrorMetadata; + EventWatchExists: ErrorMetadata; CalendarWatchExists: ErrorMetadata; NoGCalendarId: ErrorMetadata; NoResourceId: ErrorMetadata; @@ -17,8 +18,13 @@ export const SyncError: SyncErrors = { status: Status.GONE, isOperational: true, }, + EventWatchExists: { + description: "Event watch already exists", + status: Status.BAD_REQUEST, + isOperational: true, + }, CalendarWatchExists: { - description: "Watch already exists", + description: "Calendar watch already exists", status: Status.BAD_REQUEST, isOperational: true, }, diff --git a/packages/backend/src/common/services/gcal/gcal.service.ts b/packages/backend/src/common/services/gcal/gcal.service.ts index 3f31682b8..5ad7ed589 100644 --- a/packages/backend/src/common/services/gcal/gcal.service.ts +++ b/packages/backend/src/common/services/gcal/gcal.service.ts @@ -206,6 +206,25 @@ class GCalService { return response.data; } + watchCalendars = async ( + gcal: gCalendar, + params: Omit, + ) => { + const { data } = await gcal.calendarList.watch({ + syncToken: params.nextSyncToken, + requestBody: { + // reminder: address always needs to be HTTPS + address: getBaseURL() + GCAL_NOTIFICATION_ENDPOINT, + expiration: params.expiration, + id: `${params.channelId}_calendars`, + token: ENV.TOKEN_GCAL_NOTIFICATION, + type: "web_hook", + }, + }); + + return { watch: data }; + }; + watchEvents = async (gcal: gCalendar, params: Params_WatchEvents) => { const { data } = await gcal.events.watch({ calendarId: params.gCalendarId, @@ -213,7 +232,7 @@ class GCalService { // reminder: address always needs to be HTTPS address: getBaseURL() + GCAL_NOTIFICATION_ENDPOINT, expiration: params.expiration, - id: params.channelId, + id: `${params.channelId}_events`, token: ENV.TOKEN_GCAL_NOTIFICATION, type: "web_hook", }, diff --git a/packages/backend/src/sync/services/sync.service.ts b/packages/backend/src/sync/services/sync.service.ts index f8ecdc1ba..0b0248f49 100644 --- a/packages/backend/src/sync/services/sync.service.ts +++ b/packages/backend/src/sync/services/sync.service.ts @@ -28,6 +28,7 @@ import { GCalNotificationHandler } from "@backend/sync/services/notify/handler/g import { deleteWatchData, getSync, + isWatchingCalendars, isWatchingEventsByGcalId, updateSync, } from "@backend/sync/util/sync.queries"; @@ -317,6 +318,37 @@ class SyncService { } }; + startWatchingGcalCalendars = async (userId: string, gcal: gCalendar) => { + const alreadyWatching = await isWatchingCalendars(userId); + + if (alreadyWatching) { + throw error(SyncError.CalendarWatchExists, "Skipped Start Watch"); + } + + const channelId = uuidv4(); + const expiration = getChannelExpiration(); + + const watchParams: Omit = { + channelId: channelId, + expiration, + }; + + const { watch } = await gcalService.watchCalendars(gcal, watchParams); + const { resourceId } = watch; + + if (!resourceId) { + throw error(SyncError.NoResourceId, "Calendar Watch Failed"); + } + + const sync = await updateSync(Resource_Sync.CALENDAR, userId, null, { + channelId, + resourceId, + expiration, + }); + + return sync; + }; + startWatchingGcalEvents = async ( userId: string, params: { gCalendarId: string }, @@ -327,7 +359,7 @@ class SyncService { params.gCalendarId, ); if (alreadyWatching) { - throw error(SyncError.CalendarWatchExists, "Skipped Start Watch"); + throw error(SyncError.EventWatchExists, "Skipped Start Watch"); } const channelId = uuidv4(); @@ -342,7 +374,7 @@ class SyncService { const { resourceId } = watch; if (!resourceId) { - throw error(SyncError.NoResourceId, "Calendar Watch Failed"); + throw error(SyncError.NoResourceId, "Event Watch Failed"); } const sync = await updateSync( diff --git a/packages/backend/src/sync/util/sync.queries.ts b/packages/backend/src/sync/util/sync.queries.ts index 46ddfc944..59cc49d53 100644 --- a/packages/backend/src/sync/util/sync.queries.ts +++ b/packages/backend/src/sync/util/sync.queries.ts @@ -185,6 +185,18 @@ export const isWatchingEventsByGcalId = async ( return hasSyncFields; }; +export const isWatchingCalendars = async (userId: string) => { + const sync = await mongoService.sync.countDocuments({ + user: userId, + "google.calendarlist.$.channelId": { $exists: true }, + "google.calendarlist.$.expiration": { $exists: true }, + }); + + const hasSyncFields = sync === 1; + + return hasSyncFields; +}; + export const updateSync = async ( resource: Exclude, userId: string,