feat: filter generic calendars from subscription batch (#26774)

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: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Volnei Munhoz
2026-01-12 22:39:49 -03:00
committed by GitHub
co-authored by Devin AI
parent a6d7e4f107
commit a20ad44209
6 changed files with 104 additions and 0 deletions
@@ -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<CalendarSubscriptionProvider, string[]> = {
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]);
}
}
@@ -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)));
@@ -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();
});
@@ -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<SelectedCalendar[]>;
/**
@@ -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,
});
@@ -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,
});