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
59 changes: 45 additions & 14 deletions packages/lib/server/queries/organisations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,66 @@ import { MembershipRole } from "@calcom/prisma/enums";

// export type OrganisationWithMembers = Awaited<ReturnType<typeof getOrganizationMembers>>;

// also returns team
/**
* Checks if a user is an admin or owner of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating admin status
*/
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 }],
},
select: {
id: true,
role: true,
},
});

return !!membership;
}
/**
* Checks if a user is an owner of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating owner status
*/
export async function isOrganisationOwner(userId: number, orgId: number) {
return !!(await prisma.membership.findFirst({
const membership = await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
role: MembershipRole.OWNER,
},
}));
select: {
id: true,
},
});

return !!membership;
}

/**
* Checks if a user is a member of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating membership status
*/
export async function isOrganisationMember(userId: number, orgId: number) {
return !!(await prisma.membership.findUnique({
const membership = await prisma.membership.findUnique({
where: {
userId_teamId: {
userId,
teamId: orgId,
},
},
}));
select: {
id: true,
},
});

return !!membership;
}
Comment on lines 3 to 68

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The async functions isOrganisationAdmin, isOrganisationOwner, and isOrganisationMember lack error handling. Without try/catch blocks, any errors during the database query will be unhandled, potentially causing the application to crash or behave unpredictably. Wrap the async operations in try/catch to handle potential errors gracefully.

📝 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
// export type OrganisationWithMembers = Awaited<ReturnType<typeof getOrganizationMembers>>;
// also returns team
/**
* Checks if a user is an admin or owner of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating admin status
*/
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 }],
},
select: {
id: true,
role: true,
},
});
return !!membership;
}
/**
* Checks if a user is an owner of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating owner status
*/
export async function isOrganisationOwner(userId: number, orgId: number) {
return !!(await prisma.membership.findFirst({
const membership = await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
role: MembershipRole.OWNER,
},
}));
select: {
id: true,
},
});
return !!membership;
}
/**
* Checks if a user is a member of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating membership status
*/
export async function isOrganisationMember(userId: number, orgId: number) {
return !!(await prisma.membership.findUnique({
const membership = await prisma.membership.findUnique({
where: {
userId_teamId: {
userId,
teamId: orgId,
},
},
}));
select: {
id: true,
},
});
return !!membership;
}
import { MembershipRole } from "@calcom/prisma/enums";
// export type OrganisationWithMembers = Awaited<ReturnType<typeof getOrganizationMembers>>;
/**
* Checks if a user is an admin or owner of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating admin status
*/
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 }],
},
select: {
id: true,
role: true,
},
});
return !!membership;
} catch (error) {
console.error('Error checking admin status:', error);
return false;
}
}
/**
* Checks if a user is an owner of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating owner status
*/
export async function isOrganisationOwner(userId: number, orgId: number) {
try {
const membership = await prisma.membership.findFirst({
where: {
userId,
teamId: orgId,
role: MembershipRole.OWNER,
},
select: {
id: true,
},
});
return !!membership;
} catch (error) {
console.error('Error checking owner status:', error);
return false;
}
}
/**
* Checks if a user is a member of an organization
* @param userId - User ID to check
* @param orgId - Organization/Team ID
* @returns boolean indicating membership status
*/
export async function isOrganisationMember(userId: number, orgId: number) {
try {
const membership = await prisma.membership.findUnique({
where: {
userId_teamId: {
userId,
teamId: orgId,
},
},
select: {
id: true,
},
});
return !!membership;
} catch (error) {
console.error('Error checking membership status:', error);
return false;
}
}

100 changes: 100 additions & 0 deletions packages/lib/server/queries/organisations/membershipCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import type { MembershipRole } from "@calcom/prisma/enums";

/**
* In-memory cache for organization membership queries
* This helps reduce database load for frequently accessed membership data
*/

interface MembershipCacheEntry {
userId: number;
teamId: number;
role: MembershipRole;
timestamp: number;
}

const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
const membershipCache = new Map<string, MembershipCacheEntry>();

/**
* Generates cache key for membership lookup
*/
function getCacheKey(userId: number, teamId: number): string {
return `${userId}:${teamId}`;
}

/**
* Checks if cache entry is still valid
*/
function isCacheValid(entry: MembershipCacheEntry): boolean {
return Date.now() - entry.timestamp <= CACHE_TTL_MS;
}

/**
* Gets cached membership data if available and valid
*/
export function getCachedMembership(userId: number, teamId: number): MembershipCacheEntry | null {
const key = getCacheKey(userId, teamId);
const entry = membershipCache.get(key);

if (!entry) {
return null;
}

if (!isCacheValid(entry)) {
membershipCache.delete(key);
return null;
}

return entry;
}

/**
* Caches membership data
*/
export function setCachedMembership(
userId: number,
teamId: number,
role: MembershipRole
): void {
const key = getCacheKey(userId, teamId);
membershipCache.set(key, {
userId,
teamId,
role,
timestamp: Date.now(),
});
}

/**
* Invalidates cached membership for a user-team pair
*/
export function invalidateMembershipCache(userId: number, teamId: number): void {
const key = getCacheKey(userId, teamId);
membershipCache.delete(key);
}

/**
* Clears all expired cache entries
*/
export function cleanupExpiredCache(): number {
let removedCount = 0;

for (const [key, entry] of membershipCache.entries()) {
if (!isCacheValid(entry)) {
membershipCache.delete(key);
removedCount++;
}
}

return removedCount;
}

/**
* Gets current cache statistics
*/
export function getCacheStats() {
return {
size: membershipCache.size,
ttlMs: CACHE_TTL_MS,
};
}
Comment on lines +1 to +100

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The getCacheStats function should have an explicit return type for better type safety and clarity.