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
6 changes: 3 additions & 3 deletions packages/features/calendar-cache/calendar-cache.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { ICalendarCacheRepository } from "./calendar-cache.repository.inter
const log = logger.getSubLogger({ prefix: ["CalendarCacheRepository"] });

const MS_PER_DAY = 24 * 60 * 60 * 1000;
const ONE_MONTH_IN_MS = 30 * MS_PER_DAY;
const ONE_MONTH_IN_MS = 31 * MS_PER_DAY;
const CACHING_TIME = ONE_MONTH_IN_MS;

function parseKeyForCache(args: FreeBusyArgs): string {
Comment on lines 11 to 17

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The constant ONE_MONTH_IN_MS is set to 31 days, which may not accurately represent a month. Consider using an average month duration or a more precise calculation based on the current month.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const log = logger.getSubLogger({ prefix: ["CalendarCacheRepository"] });
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const ONE_MONTH_IN_MS = 30 * MS_PER_DAY;
const ONE_MONTH_IN_MS = 31 * MS_PER_DAY;
const CACHING_TIME = ONE_MONTH_IN_MS;
function parseKeyForCache(args: FreeBusyArgs): string {
const log = logger.getSubLogger({ prefix: ["CalendarCacheRepository"] });
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const ONE_MONTH_IN_MS = 30.44 * MS_PER_DAY; // Average month duration
const CACHING_TIME = ONE_MONTH_IN_MS;
function parseKeyForCache(args: FreeBusyArgs): string {

Expand Down Expand Up @@ -108,7 +108,7 @@ export class CalendarCacheRepository implements ICalendarCacheRepository {
where: {
userId,
key,
expiresAt: { gte: new Date(Date.now()) },
expiresAt: { gt: new Date(Date.now()) },
},
orderBy: {
// In case of multiple entries for same key and userId, we prefer the one with highest expiry, which will be the most updated one
Comment on lines 108 to 114

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The change from gte to gt for expiresAt may exclude entries that expire exactly at the current time, potentially causing valid cache entries to be missed. Verify if this change is intentional.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
where: {
userId,
key,
expiresAt: { gte: new Date(Date.now()) },
expiresAt: { gt: new Date(Date.now()) },
},
orderBy: {
// In case of multiple entries for same key and userId, we prefer the one with highest expiry, which will be the most updated one
where: {
userId,
key,
expiresAt: { gte: new Date(Date.now()) },
},
orderBy: {
// In case of multiple entries for same key and userId, we prefer the one with highest expiry, which will be the most updated one
}

Expand All @@ -123,7 +123,7 @@ export class CalendarCacheRepository implements ICalendarCacheRepository {
credentialId,
key,
},
expiresAt: { gte: new Date(Date.now()) },
expiresAt: { gt: new Date(Date.now()) },
},
});
}
Comment on lines 123 to 129

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] Change from gte to gt in expiresAt ensures only future entries are retrieved. Confirm this aligns with business logic.

Expand Down
4 changes: 2 additions & 2 deletions packages/features/calendar-cache/lib/getShouldServeCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CacheService {

async getShouldServeCache(shouldServeCache?: boolean | undefined, teamId?: number) {
if (typeof shouldServeCache === "boolean") return shouldServeCache;
if (!teamId) return false;
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
if (teamId) return false;
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId!, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
Comment on lines +13 to +14

Choose a reason for hiding this comment

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

correctness: 🟢 [Standard Reviewer] if (teamId) return false; inverts the intended logic: it returns false when teamId is present, preventing feature checks for valid teams and always returning false for valid input.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/features/calendar-cache/lib/getShouldServeCache.ts, lines 13-14, the logic is inverted: `if (teamId) return false;` causes the function to return false when a valid teamId is provided, which is incorrect. Change it to `if (!teamId) return false;` and remove the non-null assertion on teamId in the next line. The corrected code should check for missing teamId and only then return false.
📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if (teamId) return false;
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId!, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
if (!teamId) return false;
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);

}
}
Comment on lines 10 to 16

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The use of teamId! is unsafe as it assumes teamId is always defined, which can lead to runtime errors if teamId is undefined. Consider adding a check or handling the case where teamId might be undefined.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async getShouldServeCache(shouldServeCache?: boolean | undefined, teamId?: number) {
if (typeof shouldServeCache === "boolean") return shouldServeCache;
if (!teamId) return false;
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
if (teamId) return false;
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId!, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
}
}
constructor(private readonly dependencies: ICacheService) {}
async getShouldServeCache(shouldServeCache?: boolean | undefined, teamId?: number) {
if (typeof shouldServeCache === "boolean") return shouldServeCache;
if (teamId !== undefined) {
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
}
return false;
}

Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export class CalendarCacheWrapper implements Calendar {

if (!selectedCalendars?.length) return [];

const withSync = selectedCalendars.filter((c) => c.syncToken && c.syncSubscribedAt);
const withoutSync = selectedCalendars.filter((c) => !c.syncToken || !c.syncSubscribedAt);
const withSync = selectedCalendars.filter((c) => c.syncToken);
const withoutSync = selectedCalendars.filter((c) => !c.syncToken);

const results: EventBusyDate[] = [];

Comment on lines 75 to 82

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] Align the filtering logic in getAvailability with getAvailabilityWithTimeZones by checking both syncToken and syncSubscribedAt.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if (!selectedCalendars?.length) return [];
const withSync = selectedCalendars.filter((c) => c.syncToken && c.syncSubscribedAt);
const withoutSync = selectedCalendars.filter((c) => !c.syncToken || !c.syncSubscribedAt);
const withSync = selectedCalendars.filter((c) => c.syncToken);
const withoutSync = selectedCalendars.filter((c) => !c.syncToken);
const results: EventBusyDate[] = [];
});
if (!selectedCalendars?.length) return [];
const withSync = selectedCalendars.filter((c) => c.syncToken && c.syncSubscribedAt);
const withoutSync = selectedCalendars.filter((c) => !c.syncToken || !c.syncSubscribedAt);
const results: EventBusyDate[] = [];

Expand Down
17 changes: 8 additions & 9 deletions packages/lib/server/queries/organisations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import { MembershipRole } from "@calcom/prisma/enums";

// also returns team
export async function isOrganisationAdmin(userId: number, orgId: number) {
return (
(await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
OR: [{ role: MembershipRole.ADMIN }, { role: MembershipRole.OWNER }],
},
})) || false
);
const membership = await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
OR: [{ role: MembershipRole.ADMIN }, { role: MembershipRole.OWNER }],
},
});
return membership || false;
Comment on lines +8 to +15

Choose a reason for hiding this comment

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

correctness: 🟢 [Standard Reviewer] isOrganisationAdmin now returns the membership object or false, which may break callers expecting a boolean result and cause logic errors in permission checks.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/lib/server/queries/organisations/index.ts, lines 8-15, the function `isOrganisationAdmin` now returns the membership object or `false`, which can break callers expecting a boolean and cause permission logic errors. Change the return statement to `return !!membership;` so the function always returns a boolean.
📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const membership = await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
OR: [{ role: MembershipRole.ADMIN }, { role: MembershipRole.OWNER }],
},
});
return membership || false;
const membership = await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
OR: [{ role: MembershipRole.ADMIN }, { role: MembershipRole.OWNER }],
},
});
return !!membership;

}
export async function isOrganisationOwner(userId: number, orgId: number) {
return !!(await prisma.membership.findFirst({
Comment on lines 5 to 18

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The isOrganisationAdmin function lacks error handling for the async operation. Wrap the await call in a try/catch block to handle potential errors from the database query.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
// also returns team
export async function isOrganisationAdmin(userId: number, orgId: number) {
return (
(await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
OR: [{ role: MembershipRole.ADMIN }, { role: MembershipRole.OWNER }],
},
})) || false
);
const membership = await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
OR: [{ role: MembershipRole.ADMIN }, { role: MembershipRole.OWNER }],
},
});
return membership || false;
}
export async function isOrganisationOwner(userId: number, orgId: number) {
return !!(await prisma.membership.findFirst({
// export type OrganisationWithMembers = Awaited<ReturnType<typeof getOrganizationMembers>>;
// also returns team
export async function isOrganisationAdmin(userId: number, orgId: number) {
try {
const membership = await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
OR: [{ role: MembershipRole.ADMIN }, { role: MembershipRole.OWNER }],
},
});
return membership || false;
} catch (error) {
console.error('Error fetching membership:', error);
return false;
}
}
export async function isOrganisationOwner(userId: number, orgId: number) {
return !!(await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
role: MembershipRole.OWNER,
},
}));
}

Expand Down