Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -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[];
}

/**
Expand Down Expand Up @@ -41,4 +57,16 @@ 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 Object.keys(GENERIC_CALENDAR_SUFFIXES).flatMap(
(provider) => GENERIC_CALENDAR_SUFFIXES[provider as CalendarSubscriptionProvider]
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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);
});
Expand All @@ -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");
Expand All @@ -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({
Expand All @@ -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();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[]>;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ describe("SelectedCalendarRepository", () => {
},
},
},
AND: undefined,
},
take: 10,
});
Expand Down Expand Up @@ -160,6 +161,7 @@ describe("SelectedCalendarRepository", () => {
},
},
},
AND: undefined,
},
take: 5,
});
Expand Down Expand Up @@ -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,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Comment on lines +5 to 6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check all exports and re-exports of SelectedCalendarRepository in the selectedCalendar directory
echo "=== Searching for SelectedCalendarRepository in the selectedCalendar directory ==="
rg -n "SelectedCalendarRepository|PrismaSelectedCalendarRepository" packages/features/selectedCalendar/ --type ts

echo ""
echo "=== Checking for index/barrel exports ==="
find packages/features/selectedCalendar -name "index.ts" -o -name "*.test.ts" | head -20

Repository: qodo-benchmark/cal.com-combined-coderabbit

Length of output: 1437


Fix class name mismatch between export and test imports.

The class was renamed to PrismaSelectedCalendarRepository but the test file imports the old name SelectedCalendarRepository (line 3 of the test). This will cause an import error. Either rename the class back to SelectedCalendarRepository or update the test imports to use PrismaSelectedCalendarRepository.

🤖 Prompt for AI Agents
In
`@packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts`
around lines 5 - 6, The exported class name PrismaSelectedCalendarRepository no
longer matches the name imported by the tests (SelectedCalendarRepository),
causing an import error; fix by either renaming the class back to
SelectedCalendarRepository in the file (change PrismaSelectedCalendarRepository
→ SelectedCalendarRepository) or update the test import to use
PrismaSelectedCalendarRepository so both sides reference the same class name
(adjust the import statement in the test that currently imports
SelectedCalendarRepository).


async findById(id: string) {
Expand All @@ -19,10 +19,12 @@ export class SelectedCalendarRepository implements ISelectedCalendarRepository {
take,
teamIds,
integrations,
genericCalendarSuffixes,
}: {
take: number;
teamIds: number[];
integrations: string[];
genericCalendarSuffixes?: string[];
Comment on lines 23 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: integrations is required here but optional in interface.

The interface declares integrations?: string[] (optional), but this implementation declares integrations: string[] (required). This inconsistency could cause TypeScript errors when calling the method without the integrations parameter.

🔧 Proposed fix
   }: {
     take: number;
     teamIds: number[];
-    integrations: string[];
+    integrations?: string[];
     genericCalendarSuffixes?: string[];
   }) {

Note: If integrations should remain required, update the interface instead.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}: {
take: number;
teamIds: number[];
integrations: string[];
genericCalendarSuffixes?: string[];
}: {
take: number;
teamIds: number[];
integrations?: string[];
genericCalendarSuffixes?: string[];
🤖 Prompt for AI Agents
In
`@packages/features/selectedCalendar/repositories/SelectedCalendarRepository.ts`
around lines 23 - 27, The implementation in SelectedCalendarRepository has a
parameter typed as integrations: string[] while the corresponding interface
marks integrations as optional; update the method signature in
SelectedCalendarRepository to integrations?: string[] (or if integrations must
be required, update the interface instead) and inside the method (e.g., where
integrations is used) handle the undefined case (e.g., default to an empty array
or guard before iterating) so usage is safe; reference the parameter group
containing take, teamIds, integrations, genericCalendarSuffixes in the
SelectedCalendarRepository method.

}) {
return this.prismaClient.selectedCalendar.findMany({
where: {
Expand All @@ -36,6 +38,11 @@ export class SelectedCalendarRepository implements ISelectedCalendarRepository {
},
},
},
AND: genericCalendarSuffixes?.length
? genericCalendarSuffixes?.map((suffix) => ({
NOT: { externalId: { endsWith: suffix } },
}))
: undefined,
},
take,
});
Expand Down
Loading