From 0f3154389fb80df58b9d5ecc6fc861a75d9f7597 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 22:04:39 +0000 Subject: [PATCH 1/2] feat: filter generic calendars from subscription batch Add filtering to SelectedCalendarRepository.findNextSubscriptionBatch to exclude generic calendars (holidays, contacts, shared, imported, resources) based on their externalId suffixes. Changes: - Add GENERIC_CALENDAR_SUFFIXES constant mapping providers to their generic calendar suffixes in AdaptersFactory - Add getGenericCalendarSuffixes method to AdapterFactory interface - Update findNextSubscriptionBatch to accept and use genericCalendarSuffixes parameter to filter out calendars with matching externalId suffixes - Update CalendarSubscriptionService.checkForNewSubscriptions to pass generic calendar suffixes from the adapter factory - Update tests to cover the new filtering logic Co-Authored-By: Volnei Munhoz --- .../adapters/AdaptersFactory.ts | 26 +++++++++++++ .../lib/CalendarSubscriptionService.ts | 1 + .../CalendarSubscriptionService.test.ts | 30 ++++++++++++++ .../SelectedCalendarRepository.interface.ts | 3 ++ .../SelectedCalendarRepository.test.ts | 39 +++++++++++++++++++ .../SelectedCalendarRepository.ts | 5 +++ 6 files changed, 104 insertions(+) diff --git a/packages/features/calendar-subscription/adapters/AdaptersFactory.ts b/packages/features/calendar-subscription/adapters/AdaptersFactory.ts index 6880234ae0ac59..1819d0806a7404 100644 --- a/packages/features/calendar-subscription/adapters/AdaptersFactory.ts +++ b/packages/features/calendar-subscription/adapters/AdaptersFactory.ts @@ -4,9 +4,25 @@ import type { ICalendarSubscriptionPort } from "@calcom/features/calendar-subscr export type CalendarSubscriptionProvider = "google_calendar" | "office365_calendar"; +/** + * Generic calendar suffixes that should be excluded from subscription. + * These are special calendars (holidays, contacts, shared, imported, resources) + * that are not user's personal calendars and shouldn't be subscribed to for sync. + */ +export const GENERIC_CALENDAR_SUFFIXES: Record = { + google_calendar: [ + "@group.v.calendar.google.com", + "@group.calendar.google.com", + "@import.calendar.google.com", + "@resource.calendar.google.com", + ], + office365_calendar: [], +}; + export interface AdapterFactory { get(provider: CalendarSubscriptionProvider): ICalendarSubscriptionPort; getProviders(): CalendarSubscriptionProvider[]; + getGenericCalendarSuffixes(): string[]; } /** @@ -41,4 +57,14 @@ export class DefaultAdapterFactory implements AdapterFactory { const providers: CalendarSubscriptionProvider[] = ["google_calendar"]; return providers; } + + /** + * Returns all generic calendar suffixes that should be excluded from subscription + * across all supported providers. + * + * @returns + */ + getGenericCalendarSuffixes(): string[] { + return this.getProviders().flatMap((provider) => GENERIC_CALENDAR_SUFFIXES[provider]); + } } diff --git a/packages/features/calendar-subscription/lib/CalendarSubscriptionService.ts b/packages/features/calendar-subscription/lib/CalendarSubscriptionService.ts index a1854d56489d37..ae0c86392d6a68 100644 --- a/packages/features/calendar-subscription/lib/CalendarSubscriptionService.ts +++ b/packages/features/calendar-subscription/lib/CalendarSubscriptionService.ts @@ -392,6 +392,7 @@ export class CalendarSubscriptionService { take: 100, integrations: this.deps.adapterFactory.getProviders(), teamIds, + genericCalendarSuffixes: this.deps.adapterFactory.getGenericCalendarSuffixes(), }); log.debug("checkForNewSubscriptions", { count: rows.length }); await Promise.allSettled(rows.map(({ id }) => this.subscribe(id))); diff --git a/packages/features/calendar-subscription/lib/__tests__/CalendarSubscriptionService.test.ts b/packages/features/calendar-subscription/lib/__tests__/CalendarSubscriptionService.test.ts index 8519a2e4b69a91..e60a77179b036e 100644 --- a/packages/features/calendar-subscription/lib/__tests__/CalendarSubscriptionService.test.ts +++ b/packages/features/calendar-subscription/lib/__tests__/CalendarSubscriptionService.test.ts @@ -112,6 +112,12 @@ describe("CalendarSubscriptionService", () => { mockAdapterFactory = { get: vi.fn().mockReturnValue(mockAdapter), getProviders: vi.fn().mockReturnValue(["google_calendar", "office365_calendar"]), + getGenericCalendarSuffixes: vi.fn().mockReturnValue([ + "@group.v.calendar.google.com", + "@group.calendar.google.com", + "@import.calendar.google.com", + "@resource.calendar.google.com", + ]), }; mockSelectedCalendarRepository = { @@ -385,6 +391,12 @@ describe("CalendarSubscriptionService", () => { take: 100, integrations: ["google_calendar", "office365_calendar"], teamIds: [1, 2, 3], + genericCalendarSuffixes: [ + "@group.v.calendar.google.com", + "@group.calendar.google.com", + "@import.calendar.google.com", + "@resource.calendar.google.com", + ], }); expect(subscribeSpy).toHaveBeenCalledWith(mockSelectedCalendar.id); }); @@ -411,6 +423,12 @@ describe("CalendarSubscriptionService", () => { take: 100, integrations: ["google_calendar", "office365_calendar"], teamIds: [10, 20], + genericCalendarSuffixes: [ + "@group.v.calendar.google.com", + "@group.calendar.google.com", + "@import.calendar.google.com", + "@resource.calendar.google.com", + ], }); expect(subscribeSpy).toHaveBeenCalledTimes(2); expect(subscribeSpy).toHaveBeenCalledWith("calendar-with-cache"); @@ -437,6 +455,12 @@ describe("CalendarSubscriptionService", () => { take: 100, integrations: ["google_calendar", "office365_calendar"], teamIds: [teamId], + genericCalendarSuffixes: [ + "@group.v.calendar.google.com", + "@group.calendar.google.com", + "@import.calendar.google.com", + "@resource.calendar.google.com", + ], }); expect(mockSelectedCalendarRepository.findNextSubscriptionBatch).not.toHaveBeenCalledWith( expect.objectContaining({ @@ -463,6 +487,12 @@ describe("CalendarSubscriptionService", () => { take: 100, integrations: ["google_calendar", "office365_calendar"], teamIds: [], + genericCalendarSuffixes: [ + "@group.v.calendar.google.com", + "@group.calendar.google.com", + "@import.calendar.google.com", + "@resource.calendar.google.com", + ], }); expect(subscribeSpy).not.toHaveBeenCalled(); }); diff --git a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.interface.ts b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.interface.ts index 2d119385175042..d4090c30d57c24 100644 --- a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.interface.ts +++ b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.interface.ts @@ -21,15 +21,18 @@ export interface ISelectedCalendarRepository { * * @param take the number of calendars to take * @param integrations the list of integrations + * @param genericCalendarSuffixes the list of generic calendar suffixes to exclude */ findNextSubscriptionBatch({ take, teamIds, integrations, + genericCalendarSuffixes, }: { take: number; teamIds: number[]; integrations?: string[]; + genericCalendarSuffixes?: string[]; }): Promise; /** diff --git a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.test.ts b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.test.ts index a963fe706a6c91..141e4e74cec4ae 100644 --- a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.test.ts +++ b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.test.ts @@ -131,6 +131,7 @@ describe("SelectedCalendarRepository", () => { }, }, }, + AND: undefined, }, take: 10, }); @@ -160,6 +161,7 @@ describe("SelectedCalendarRepository", () => { }, }, }, + AND: undefined, }, take: 5, }); @@ -189,6 +191,43 @@ describe("SelectedCalendarRepository", () => { }, }, }, + AND: undefined, + }, + take: 10, + }); + + expect(result).toEqual(mockCalendars); + }); + + test("should filter out generic calendars when genericCalendarSuffixes is provided", async () => { + const mockCalendars = [mockSelectedCalendar]; + vi.mocked(mockPrismaClient.selectedCalendar.findMany).mockResolvedValue(mockCalendars); + + const genericSuffixes = ["@group.v.calendar.google.com", "@group.calendar.google.com"]; + + const result = await repository.findNextSubscriptionBatch({ + take: 10, + teamIds: [1, 2], + integrations: ["google_calendar"], + genericCalendarSuffixes: genericSuffixes, + }); + + expect(mockPrismaClient.selectedCalendar.findMany).toHaveBeenCalledWith({ + where: { + integration: { in: ["google_calendar"] }, + OR: [{ syncSubscribedAt: null }, { channelExpiration: { lte: expect.any(Date) } }], + user: { + teams: { + some: { + teamId: { in: [1, 2] }, + accepted: true, + }, + }, + }, + AND: [ + { NOT: { externalId: { endsWith: "@group.v.calendar.google.com" } } }, + { NOT: { externalId: { endsWith: "@group.calendar.google.com" } } }, + ], }, take: 10, }); diff --git a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts index d42b4fc18e5ff8..e4f50fd89980b9 100644 --- a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts +++ b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts @@ -19,10 +19,12 @@ export class SelectedCalendarRepository implements ISelectedCalendarRepository { take, teamIds, integrations, + genericCalendarSuffixes, }: { take: number; teamIds: number[]; integrations: string[]; + genericCalendarSuffixes?: string[]; }) { return this.prismaClient.selectedCalendar.findMany({ where: { @@ -36,6 +38,9 @@ export class SelectedCalendarRepository implements ISelectedCalendarRepository { }, }, }, + AND: genericCalendarSuffixes?.map((suffix) => ({ + NOT: { externalId: { endsWith: suffix } }, + })), }, take, }); From 41aeb830d5bbf59e0ee4ebe437d5f15b6cc30bb6 Mon Sep 17 00:00:00 2001 From: tomerqodo Date: Sun, 25 Jan 2026 12:02:00 +0200 Subject: [PATCH 2/2] update pr --- .../calendar-subscription/adapters/AdaptersFactory.ts | 4 +++- .../repositories/SelectedCalendarRepository.ts | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/features/calendar-subscription/adapters/AdaptersFactory.ts b/packages/features/calendar-subscription/adapters/AdaptersFactory.ts index 1819d0806a7404..b43c28c508c3b7 100644 --- a/packages/features/calendar-subscription/adapters/AdaptersFactory.ts +++ b/packages/features/calendar-subscription/adapters/AdaptersFactory.ts @@ -65,6 +65,8 @@ export class DefaultAdapterFactory implements AdapterFactory { * @returns */ getGenericCalendarSuffixes(): string[] { - return this.getProviders().flatMap((provider) => GENERIC_CALENDAR_SUFFIXES[provider]); + return Object.keys(GENERIC_CALENDAR_SUFFIXES).flatMap( + (provider) => GENERIC_CALENDAR_SUFFIXES[provider as CalendarSubscriptionProvider] + ); } } diff --git a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts index e4f50fd89980b9..1e7bcd9df36d9a 100644 --- a/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts +++ b/packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts @@ -2,7 +2,7 @@ import type { ISelectedCalendarRepository } from "@calcom/features/selectedCalen import type { PrismaClient } from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; -export class SelectedCalendarRepository implements ISelectedCalendarRepository { +export class PrismaSelectedCalendarRepository implements ISelectedCalendarRepository { constructor(private prismaClient: PrismaClient) {} async findById(id: string) { @@ -38,9 +38,11 @@ export class SelectedCalendarRepository implements ISelectedCalendarRepository { }, }, }, - AND: genericCalendarSuffixes?.map((suffix) => ({ - NOT: { externalId: { endsWith: suffix } }, - })), + AND: genericCalendarSuffixes?.length + ? genericCalendarSuffixes?.map((suffix) => ({ + NOT: { externalId: { endsWith: suffix } }, + })) + : undefined, }, take, });