refactor: apply biome formatting to packages/trpc (#27928)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eunjae Lee
2026-02-16 12:46:42 -03:00
committed by GitHub
co-authored by Claude Opus 4.6
parent f8a93414a5
commit 21d28c9747
80 changed files with 498 additions and 454 deletions
+2 -2
View File
@@ -56,8 +56,8 @@ export const trpc: CreateTRPCNext<AppRouter, NextPageContext, null> = createTRPC
typeof window !== "undefined"
? "/api/trpc"
: process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}/api/trpc`
: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/trpc`;
? `https://${process.env.VERCEL_URL}/api/trpc`
: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/trpc`;
/**
* If you want to use SSR, you need to use the server's full URL
+16 -17
View File
@@ -22,23 +22,22 @@ export type CreateInnerContextOptions = {
sourceIp?: string;
session?: Session | null;
locale: string;
user?:
| Omit<
PrismaUser,
| "locale"
| "twoFactorSecret"
| "emailVerified"
| "password"
| "identityProviderId"
| "invitedTo"
| "allowDynamicBooking"
| "verified"
> & {
locale: Exclude<PrismaUser["locale"], null>;
credentials?: Credential[];
selectedCalendars?: Partial<SelectedCalendar>[];
rawAvatar?: string;
};
user?: Omit<
PrismaUser,
| "locale"
| "twoFactorSecret"
| "emailVerified"
| "password"
| "identityProviderId"
| "invitedTo"
| "allowDynamicBooking"
| "verified"
> & {
locale: Exclude<PrismaUser["locale"], null>;
credentials?: Credential[];
selectedCalendars?: Partial<SelectedCalendar>[];
rawAvatar?: string;
};
i18n?: Awaited<ReturnType<typeof serverSideTranslations>>;
} & Partial<CreateContextOptions>;
@@ -45,10 +45,10 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe<S
safeStringify({ user, userFromDb, upId })
);
const { email, username, id, uuid } = user;
if (!email || !id) {
return null; // should we return null here?
}
const { email, username, id, uuid } = user;
if (!email || !id) {
return null; // should we return null here?
}
const userMetaData = userMetadata.parse(user.metadata || {});
const orgMetadata = teamMetadataSchema.parse(user.profile?.organization?.metadata || {});
@@ -71,20 +71,20 @@ export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe<S
requestedSlug: orgMetadata?.requestedSlug ?? null,
};
return {
...user,
avatar: `${WEBAPP_URL}/${user.username}/avatar.png${organization.id ? `?orgId=${organization.id}` : ""}`,
// TODO: OrgNewSchema - later - We could consolidate the props in user.profile?.organization as organization is a profile thing now.
organization,
organizationId: organization.id,
id,
uuid,
email,
username,
locale,
defaultBookerLayouts: userMetaData?.defaultBookerLayouts || null,
requiresBookerEmailVerification: user.requiresBookerEmailVerification,
};
return {
...user,
avatar: `${WEBAPP_URL}/${user.username}/avatar.png${organization.id ? `?orgId=${organization.id}` : ""}`,
// TODO: OrgNewSchema - later - We could consolidate the props in user.profile?.organization as organization is a profile thing now.
organization,
organizationId: organization.id,
id,
uuid,
email,
username,
locale,
defaultBookerLayouts: userMetaData?.defaultBookerLayouts || null,
requiresBookerEmailVerification: user.requiresBookerEmailVerification,
};
}
export type UserFromSession = Awaited<ReturnType<typeof getUserFromSession>>;
@@ -134,7 +134,8 @@ describe("Feature Opt-In PBAC Procedures", () => {
permissions.forEach(({ permission, operation }) => {
it(`should use ${permission} for ${operation} operations`, async () => {
createTeamPbacProcedure(permission);
const middleware = mockAuthedProcedure.use.mock.calls[mockAuthedProcedure.use.mock.calls.length - 1][0];
const middleware =
mockAuthedProcedure.use.mock.calls[mockAuthedProcedure.use.mock.calls.length - 1][0];
mockCheckPermission.mockResolvedValue(true);
@@ -298,7 +299,8 @@ describe("Feature Opt-In PBAC Procedures", () => {
permissions.forEach(({ permission, operation }) => {
it(`should use ${permission} for ${operation} operations`, async () => {
createOrgPbacProcedure(permission);
const middleware = mockAuthedProcedure.use.mock.calls[mockAuthedProcedure.use.mock.calls.length - 1][0];
const middleware =
mockAuthedProcedure.use.mock.calls[mockAuthedProcedure.use.mock.calls.length - 1][0];
const mockCtx = {
user: {
@@ -27,7 +27,7 @@ const getHandler = async <
T extends {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
default: Function;
}
},
>(
/**
* The name of the handler in cache. It has to be unique across all routes
@@ -407,7 +407,9 @@ describe("entityPrismaWhereClause Integration Tests", () => {
ctx: { prisma, user },
input: { id: "test" },
});
} catch { void 0; }
} catch {
void 0;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
whereClause = (prisma.app_RoutingForms_Form.deleteMany as any).mock.calls[0][0].where;
@@ -440,15 +442,16 @@ describe("entityPrismaWhereClause Integration Tests", () => {
input: { id: "test" },
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const formQueryWhere = (prisma.app_RoutingForms_Form.findFirst as any).mock.calls[0][0].where
.AND[0];
const formQueryWhere = (prisma.app_RoutingForms_Form.findFirst as any).mock.calls[0][0].where.AND[0];
try {
await deleteFormHandler({
ctx: { prisma, user },
input: { id: "test" },
});
} catch { void 0; }
} catch {
void 0;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deleteFormWhere = (prisma.app_RoutingForms_Form.deleteMany as any).mock.calls[0][0].where;
@@ -363,8 +363,7 @@ export const formMutationHandler = async ({ ctx, input }: FormMutationHandlerOpt
});
}
let fields: NonNullable<typeof fieldsParsed.data>,
routes: NonNullable<typeof routesParsed.data>;
let fields: NonNullable<typeof fieldsParsed.data>, routes: NonNullable<typeof routesParsed.data>;
if (shouldConnect) {
routes = [
// This connected route would automatically link the fields
@@ -67,7 +67,13 @@ export const teamsAndUserProfilesQuery = async ({ ctx, input }: TeamsAndUserProf
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
}
let teamsData: typeof user.teams extends (infer T)[] ? (T & { team: T extends { team: infer U } ? U & { metadata: ReturnType<typeof teamMetadataSchema.parse> } : never })[] : never;
let teamsData: typeof user.teams extends (infer T)[]
? (T & {
team: T extends { team: infer U }
? U & { metadata: ReturnType<typeof teamMetadataSchema.parse> }
: never;
})[]
: never;
if (input?.includeOrg) {
teamsData = user.teams
@@ -5,7 +5,9 @@ export type TUserEmailVerificationRequiredSchema = {
email: string;
};
export const ZUserEmailVerificationRequiredSchema: z.ZodType<TUserEmailVerificationRequiredSchema> = z.object({
userSessionEmail: z.string().optional(),
email: z.string(),
});
export const ZUserEmailVerificationRequiredSchema: z.ZodType<TUserEmailVerificationRequiredSchema> = z.object(
{
userSessionEmail: z.string().optional(),
email: z.string(),
}
);
@@ -13,14 +13,16 @@ export type TEventInputSchemaInput = {
export type TEventInputSchema = GetPublicEventInput;
export const ZEventInputSchema: z.ZodType<TEventInputSchema, z.ZodTypeDef, TEventInputSchemaInput> = z.object({
username: z.string(),
eventSlug: z.string(),
isTeamEvent: z.boolean().optional(),
org: z.string().nullable(),
/**
* Informs that the event request has been sent from a page that was reached by a redirect from non-org link(i.e. app.cal.com/username redirected to acme.cal.com/username)
* Based on this decision like whether to allow unpublished organization's event to be served or not can be made.
*/
fromRedirectOfNonOrgLink: z.boolean().optional().default(false),
});
export const ZEventInputSchema: z.ZodType<TEventInputSchema, z.ZodTypeDef, TEventInputSchemaInput> = z.object(
{
username: z.string(),
eventSlug: z.string(),
isTeamEvent: z.boolean().optional(),
org: z.string().nullable(),
/**
* Informs that the event request has been sent from a page that was reached by a redirect from non-org link(i.e. app.cal.com/username redirected to acme.cal.com/username)
* Based on this decision like whether to allow unpublished organization's event to be served or not can be made.
*/
fromRedirectOfNonOrgLink: z.boolean().optional().default(false),
}
);
@@ -23,12 +23,10 @@ export const watchlistRouter = router({
const { deleteWatchlistEntryHandler: handler } = await import("./delete.handler");
return handler(opts);
}),
bulkDelete: authedAdminProcedure
.input(ZBulkDeleteWatchlistEntriesInputSchema)
.mutation(async (opts) => {
const { bulkDeleteWatchlistEntriesHandler: handler } = await import("./bulkDelete.handler");
return handler(opts);
}),
bulkDelete: authedAdminProcedure.input(ZBulkDeleteWatchlistEntriesInputSchema).mutation(async (opts) => {
const { bulkDeleteWatchlistEntriesHandler: handler } = await import("./bulkDelete.handler");
return handler(opts);
}),
getDetails: authedAdminProcedure.input(ZGetWatchlistEntryDetailsInputSchema).query(async (opts) => {
const { getWatchlistEntryDetailsHandler: handler } = await import("./getDetails.handler");
return handler(opts);
@@ -12,4 +12,4 @@ export const ZListCallsInputSchema = z.object({
.optional(),
});
export type TListCallsInputSchema = z.infer<typeof ZListCallsInputSchema>;
export type TListCallsInputSchema = z.infer<typeof ZListCallsInputSchema>;
@@ -12,4 +12,4 @@ export const listVoicesHandler = async ({ ctx }: ListVoicesHandlerOptions) => {
const aiService = createDefaultAIPhoneServiceProvider();
return await aiService.listVoices();
};
};
@@ -232,4 +232,3 @@ describe("appByIdHandler", () => {
expect(result).not.toHaveProperty("key");
});
});
@@ -68,10 +68,13 @@ export const listLocalHandler = async ({ ctx, input }: ListLocalOptions) => {
// it is important to avoid string to string comparisons as much as we can
if (keysSchema !== undefined) {
// TODO: Why don't we parse with schema here? Not doing it makes default() not work in schema.
Object.values(keysSchema.keyof()._def.values).reduce((keysObject, key) => {
keys[key as string] = "";
return keysObject;
}, {} as Record<string, string>);
Object.values(keysSchema.keyof()._def.values).reduce(
(keysObject, key) => {
keys[key as string] = "";
return keysObject;
},
{} as Record<string, string>
);
}
return {
@@ -17,10 +17,7 @@ type CreateAttributeSyncOptions = {
input: ZCreateAttributeSyncSchema;
};
const createAttributeSyncHandler = async ({
ctx,
input,
}: CreateAttributeSyncOptions) => {
const createAttributeSyncHandler = async ({ ctx, input }: CreateAttributeSyncOptions) => {
const org = ctx.user.organization;
if (!org?.id) {
@@ -33,10 +30,7 @@ const createAttributeSyncHandler = async ({
const integrationAttributeSyncService = getIntegrationAttributeSyncService();
try {
return await integrationAttributeSyncService.createAttributeSync(
input,
org.id
);
return await integrationAttributeSyncService.createAttributeSync(input, org.id);
} catch (error) {
if (error instanceof CredentialNotFoundError) {
throw new TRPCError({
@@ -16,10 +16,7 @@ type UpdateAttributeSyncOptions = {
input: ZUpdateAttributeSyncSchema;
};
const updateAttributeSyncHandler = async ({
ctx,
input,
}: UpdateAttributeSyncOptions) => {
const updateAttributeSyncHandler = async ({ ctx, input }: UpdateAttributeSyncOptions) => {
const org = ctx.user.organization;
if (!org?.id) {
@@ -32,8 +29,7 @@ const updateAttributeSyncHandler = async ({
const integrationAttributeSyncService = getIntegrationAttributeSyncService();
// Verify the sync exists and belongs to the user's organization
const integrationAttributeSync =
await integrationAttributeSyncService.getById(input.id);
const integrationAttributeSync = await integrationAttributeSyncService.getById(input.id);
if (!integrationAttributeSync) throw new TRPCError({ code: "NOT_FOUND" });
@@ -103,7 +103,9 @@ const editAttributesHandler = async ({ input, ctx }: GetOptions) => {
await validateOptionsBelongToAttribute(options, attributes.id);
await prisma.$transaction(async (tx) => {
const updateOptions = options.filter((option): option is typeof option & { id: string } => option.id !== undefined && option.id !== "");
const updateOptions = options.filter(
(option): option is typeof option & { id: string } => option.id !== undefined && option.id !== ""
);
const updatedOptionsIds = updateOptions.map((option) => option.id);
// We need to delete all options that are not present in this UpdateOptions.id (as they have been deleted)
await tx.attributeOption.deleteMany({
@@ -2,7 +2,7 @@
* Ensures that contains has no non-existent sub-options
*/
export function getOptionsWithValidContains<
T extends { id?: string; value: string; contains?: string[]; isGroup?: boolean }
T extends { id?: string; value: string; contains?: string[]; isGroup?: boolean },
>(options: T[]): T[] {
return options
.filter((obj, index, self) => index === self.findIndex((t) => t.value === obj.value))
@@ -14,9 +14,11 @@ vi.mock("@calcom/lib/hasEditPermissionForUser", () => ({
}));
vi.mock("@calcom/features/schedules/repositories/ScheduleRepository", () => ({
ScheduleRepository: vi.fn().mockImplementation(function() { return {
getDefaultScheduleId: vi.fn().mockResolvedValue(DEFAULT_SCHEDULE_ID),
}; }),
ScheduleRepository: vi.fn().mockImplementation(function () {
return {
getDefaultScheduleId: vi.fn().mockResolvedValue(DEFAULT_SCHEDULE_ID),
};
}),
}));
import { hasReadPermissionsForUserId } from "@calcom/lib/hasEditPermissionForUser";
@@ -20,7 +20,10 @@ type GetOptions = {
export const getAllSchedulesByUserIdHandler = async ({ ctx, input }: GetOptions) => {
const { user } = ctx;
const isCurrentUserPartOfTeam = await hasReadPermissionsForUserId({ memberId: input?.userId, userId: user.id });
const isCurrentUserPartOfTeam = await hasReadPermissionsForUserId({
memberId: input?.userId,
userId: user.id,
});
const isCurrentUserOwner = input?.userId === user.id;
@@ -137,7 +137,9 @@ async function getAllCredentialsIncludeServiceAccountKey({
}) {
const credentials = await getUsersCredentialsIncludeServiceAccountKey(user);
let conferenceCredential: Awaited<ReturnType<typeof CredentialRepository.findFirstByIdWithKeyAndUser>> | undefined;
let conferenceCredential:
| Awaited<ReturnType<typeof CredentialRepository.findFirstByIdWithKeyAndUser>>
| undefined;
if (conferenceCredentialId) {
// Validate that the credential is accessible before fetching it
@@ -225,9 +225,7 @@ describe("getBookings - integration", () => {
});
const testBookingIds = [booking1.id, booking2.id, booking3.id];
const relevantBookingIds = result.bookings
.map((b) => b.id)
.filter((id) => testBookingIds.includes(id));
const relevantBookingIds = result.bookings.map((b) => b.id).filter((id) => testBookingIds.includes(id));
const uniqueIds = new Set(relevantBookingIds);
expect(relevantBookingIds.length).toBe(uniqueIds.size);
@@ -4,61 +4,64 @@ import type { PrismaClient } from "@calcom/prisma/client";
import { TRPCError } from "@trpc/server";
import { getBookingHistoryViewerService } from "@calcom/features/booking-audit/di/BookingHistoryViewerService.container";
import { BookingAuditErrorCode, BookingAuditPermissionError } from "@calcom/features/booking-audit/lib/service/BookingAuditAccessService";
import {
BookingAuditErrorCode,
BookingAuditPermissionError,
} from "@calcom/features/booking-audit/lib/service/BookingAuditAccessService";
import { getTranslation } from "@calcom/lib/server/i18n";
import type { TrpcSessionUser } from "../../../types";
import type { TGetBookingHistoryInputSchema } from "./getBookingHistory.schema";
type GetBookingHistoryOptions = {
ctx: {
user: NonNullable<TrpcSessionUser>;
prisma: PrismaClient;
};
input: TGetBookingHistoryInputSchema;
ctx: {
user: NonNullable<TrpcSessionUser>;
prisma: PrismaClient;
};
input: TGetBookingHistoryInputSchema;
};
const getErrorMessage = (code: BookingAuditErrorCode, t: TFunction): string => {
switch (code) {
case BookingAuditErrorCode.ORGANIZATION_ID_REQUIRED:
return t("audit_logs_organization_required");
case BookingAuditErrorCode.BOOKING_NOT_FOUND_OR_PERMISSION_DENIED:
return t("audit_logs_booking_not_found_or_permission_denied");
case BookingAuditErrorCode.BOOKING_HAS_NO_OWNER:
return t("audit_logs_booking_has_no_owner");
case BookingAuditErrorCode.OWNER_NOT_IN_ORGANIZATION:
return t("audit_logs_owner_not_in_organization");
case BookingAuditErrorCode.PERMISSION_DENIED:
return t("audit_logs_permission_denied");
default:
return t("audit_logs_permission_check_error");
}
switch (code) {
case BookingAuditErrorCode.ORGANIZATION_ID_REQUIRED:
return t("audit_logs_organization_required");
case BookingAuditErrorCode.BOOKING_NOT_FOUND_OR_PERMISSION_DENIED:
return t("audit_logs_booking_not_found_or_permission_denied");
case BookingAuditErrorCode.BOOKING_HAS_NO_OWNER:
return t("audit_logs_booking_has_no_owner");
case BookingAuditErrorCode.OWNER_NOT_IN_ORGANIZATION:
return t("audit_logs_owner_not_in_organization");
case BookingAuditErrorCode.PERMISSION_DENIED:
return t("audit_logs_permission_denied");
default:
return t("audit_logs_permission_check_error");
}
};
export const getBookingHistoryHandler = async ({ ctx, input }: GetBookingHistoryOptions) => {
const { user } = ctx;
const { bookingUid } = input;
const { user } = ctx;
const { bookingUid } = input;
const t = await getTranslation(user.locale ?? "en", "common");
const bookingHistoryViewerService = getBookingHistoryViewerService();
const t = await getTranslation(user.locale ?? "en", "common");
const bookingHistoryViewerService = getBookingHistoryViewerService();
try {
const result = await bookingHistoryViewerService.getHistoryForBooking({
bookingUid,
userId: user.id,
userEmail: user.email,
userTimeZone: user.timeZone,
organizationId: user.organizationId,
});
try {
const result = await bookingHistoryViewerService.getHistoryForBooking({
bookingUid,
userId: user.id,
userEmail: user.email,
userTimeZone: user.timeZone,
organizationId: user.organizationId,
});
return result;
} catch (error) {
if (error instanceof BookingAuditPermissionError) {
throw new TRPCError({
code: "FORBIDDEN",
message: getErrorMessage(error.code, t),
});
}
throw error;
return result;
} catch (error) {
if (error instanceof BookingAuditPermissionError) {
throw new TRPCError({
code: "FORBIDDEN",
message: getErrorMessage(error.code, t),
});
}
throw error;
}
};
@@ -58,9 +58,15 @@ describe("reportBookingHandler", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(BookingRepository).mockImplementation(function() { return mockBookingRepo; });
vi.mocked(PrismaBookingReportRepository).mockImplementation(function() { return mockReportRepo; });
vi.mocked(BookingAccessService).mockImplementation(function() { return mockBookingAccessService; });
vi.mocked(BookingRepository).mockImplementation(function () {
return mockBookingRepo;
});
vi.mocked(PrismaBookingReportRepository).mockImplementation(function () {
return mockReportRepo;
});
vi.mocked(BookingAccessService).mockImplementation(function () {
return mockBookingAccessService;
});
mockReportRepo.createReport.mockResolvedValue({ id: "new-report" });
});
@@ -27,7 +27,7 @@ export default async function handler({
return ensureNoServiceAccountKey({
...delegation,
serviceAccountClientId: serviceAccountKey.success ? serviceAccountKey.data?.client_id ?? null : null,
serviceAccountClientId: serviceAccountKey.success ? (serviceAccountKey.data?.client_id ?? null) : null,
});
});
}
@@ -56,9 +56,9 @@ describe("toggleDelegationCredentialEnabled - Security Fix", () => {
vi.mocked(DelegationCredentialRepository.findById).mockResolvedValue(mockCredential);
await expect(
toggleDelegationCredentialEnabled(userWithoutOrg, input)
).rejects.toThrow("You must be part of an organization to toggle a delegation credential");
await expect(toggleDelegationCredentialEnabled(userWithoutOrg, input)).rejects.toThrow(
"You must be part of an organization to toggle a delegation credential"
);
expect(DelegationCredentialRepository.updateById).not.toHaveBeenCalled();
});
@@ -84,9 +84,9 @@ describe("toggleDelegationCredentialEnabled - Security Fix", () => {
vi.mocked(DelegationCredentialRepository.findById).mockResolvedValue(org2Credential);
await expect(
toggleDelegationCredentialEnabled(userFromOrg1, input)
).rejects.toThrow("Delegation credential not found");
await expect(toggleDelegationCredentialEnabled(userFromOrg1, input)).rejects.toThrow(
"Delegation credential not found"
);
expect(DelegationCredentialRepository.updateById).not.toHaveBeenCalled();
});
@@ -146,8 +146,8 @@ describe("toggleDelegationCredentialEnabled - Security Fix", () => {
vi.mocked(DelegationCredentialRepository.findById).mockResolvedValue(null);
await expect(
toggleDelegationCredentialEnabled(user, input)
).rejects.toThrow("Delegation credential not found");
await expect(toggleDelegationCredentialEnabled(user, input)).rejects.toThrow(
"Delegation credential not found"
);
});
});
});
@@ -37,9 +37,11 @@ vi.mock("@calcom/features/profile/repositories/ProfileRepository", () => ({
const mockFilterTeamsByEventTypeReadPermission = vi.fn();
vi.mock("../teamAccessUseCase", () => ({
TeamAccessUseCase: vi.fn().mockImplementation(function() { return {
filterTeamsByEventTypeReadPermission: mockFilterTeamsByEventTypeReadPermission,
}; }),
TeamAccessUseCase: vi.fn().mockImplementation(function () {
return {
filterTeamsByEventTypeReadPermission: mockFilterTeamsByEventTypeReadPermission,
};
}),
}));
vi.mock("@calcom/features/ee/organizations/lib/getBookerUrlServer", () => ({
@@ -44,7 +44,9 @@ describe("createEventPbacProcedure", () => {
checkPermission: vi.fn(),
};
vi.mocked(PermissionCheckService).mockImplementation(function() { return mockPermissionCheckService as any; });
vi.mocked(PermissionCheckService).mockImplementation(function () {
return mockPermissionCheckService as any;
});
});
describe("personal events", () => {
@@ -16,10 +16,13 @@ export const filterQuerySchemaStrict: z.ZodType<TFilterQuerySchemaStrict> = z.ob
schedulingTypes: z.nativeEnum(SchedulingType).array().optional(),
});
export type TEventTypeInputSchema = {
filters?: TFilterQuerySchemaStrict;
forRoutingForms?: boolean;
} | null | undefined;
export type TEventTypeInputSchema =
| {
filters?: TFilterQuerySchemaStrict;
forRoutingForms?: boolean;
}
| null
| undefined;
export const ZEventTypeInputSchema: z.ZodType<TEventTypeInputSchema> = z
.object({
@@ -46,7 +49,11 @@ export type TGetEventTypesFromGroupSchema = {
searchQuery?: string;
};
export const ZGetEventTypesFromGroupSchema: z.ZodType<TGetEventTypesFromGroupSchema, z.ZodTypeDef, TGetEventTypesFromGroupSchemaInput> = z.object({
export const ZGetEventTypesFromGroupSchema: z.ZodType<
TGetEventTypesFromGroupSchema,
z.ZodTypeDef,
TGetEventTypesFromGroupSchemaInput
> = z.object({
filters: filterQuerySchemaStrict.optional(),
forRoutingForms: z.boolean().optional(),
cursor: z.number().nullish(),
@@ -83,10 +83,13 @@ export const getUserEventGroups = async ({ ctx, input }: GetByViewerOptions) =>
});
const teamPermissionsArray = await Promise.all(teamPermissionChecks);
const teamPermissions = teamPermissionsArray.reduce((acc, item) => {
acc[item.teamId] = item.permissions;
return acc;
}, {} as Record<number, { canCreateEventType: boolean }>);
const teamPermissions = teamPermissionsArray.reduce(
(acc, item) => {
acc[item.teamId] = item.permissions;
return acc;
},
{} as Record<number, { canCreateEventType: boolean }>
);
return {
eventTypeGroups: filteredEventTypeGroups,
@@ -49,8 +49,7 @@ describe("duplicateHandler", () => {
await expect(duplicateHandler({ ctx, input })).rejects.toThrow(
new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Error duplicating event type PrismaClientKnownRequestError: Unique constraint failed",
message: "Error duplicating event type PrismaClientKnownRequestError: Unique constraint failed",
})
);
});
@@ -226,14 +226,13 @@ export const duplicateHandler = async ({ ctx, input }: DuplicateOptions) => {
};
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
if (Array.isArray(error.meta?.target) && error.meta?.target.includes("slug")) {
throw new TRPCError({
code: "CONFLICT",
message: "duplicate_event_slug_conflict",
});
}
throw new TRPCError({
code: "CONFLICT",
message: "Unique constraint violation while creating a duplicate event.",
@@ -25,9 +25,7 @@ const findCredentialIdForLocationType = (
locationType: string,
credentials: { id: number; type: string; appId: string | null }[]
): number | null => {
const appMeta = Object.values(appStoreMetadata).find(
(app) => app.appData?.location?.type === locationType
);
const appMeta = Object.values(appStoreMetadata).find((app) => app.appData?.location?.type === locationType);
if (!appMeta) return null;
const matchingCredential = credentials.find(
@@ -6,9 +6,11 @@ import { TeamAccessUseCase } from "./teamAccessUseCase";
// Mock the PermissionCheckService
vi.mock("@calcom/features/pbac/services/permission-check.service", () => ({
PermissionCheckService: vi.fn().mockImplementation(function() { return {
checkPermission: vi.fn(),
}; }),
PermissionCheckService: vi.fn().mockImplementation(function () {
return {
checkPermission: vi.fn(),
};
}),
}));
describe("TeamAccessUseCase", () => {
@@ -251,18 +251,21 @@ export function ensureUniqueBookingFields(fields: TUpdateInputSchema["bookingFie
return;
}
fields.reduce((discoveredFields, field) => {
if (discoveredFields[field.name]) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Duplicate booking field name: ${field.name}`,
});
}
fields.reduce(
(discoveredFields, field) => {
if (discoveredFields[field.name]) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Duplicate booking field name: ${field.name}`,
});
}
discoveredFields[field.name] = true;
discoveredFields[field.name] = true;
return discoveredFields;
}, {} as Record<string, true>);
return discoveredFields;
},
{} as Record<string, true>
);
}
export function ensureEmailOrPhoneNumberIsPresent(fields: TUpdateInputSchema["bookingFields"]) {
@@ -36,4 +36,3 @@ export const holidaysRouter = router({
return checkConflictsHandler({ ctx, input });
}),
});
@@ -6,4 +6,3 @@ export const ZCheckConflictsSchema = z.object({
});
export type TCheckConflictsSchema = z.infer<typeof ZCheckConflictsSchema>;
@@ -3,4 +3,3 @@ import { z } from "zod";
export const ZGetUserSettingsSchema = z.object({}).optional();
export type TGetUserSettingsSchema = z.infer<typeof ZGetUserSettingsSchema>;
@@ -6,4 +6,3 @@ export const ZToggleHolidaySchema = z.object({
});
export type TToggleHolidaySchema = z.infer<typeof ZToggleHolidaySchema>;
@@ -7,4 +7,3 @@ export const ZUpdateSettingsSchema = z.object({
});
export type TUpdateSettingsSchema = z.infer<typeof ZUpdateSettingsSchema>;
@@ -13,9 +13,8 @@ type MeOptions = {
export const platformMeHandler = async ({ ctx }: MeOptions) => {
const { user: sessionUser } = ctx;
const allUserEnrichedProfiles = await ProfileRepository.findAllProfilesForUserIncludingMovedUser(
sessionUser
);
const allUserEnrichedProfiles =
await ProfileRepository.findAllProfilesForUserIncludingMovedUser(sessionUser);
const mainProfile =
allUserEnrichedProfiles.find((profile) => sessionUser.movedToProfileId === profile.id) ||
@@ -84,7 +84,11 @@ export type TUpdateProfileInputSchema = {
}[];
};
export const ZUpdateProfileInputSchema: z.ZodType<TUpdateProfileInputSchema, z.ZodTypeDef, TUpdateProfileInputSchemaInput> = z.object({
export const ZUpdateProfileInputSchema: z.ZodType<
TUpdateProfileInputSchema,
z.ZodTypeDef,
TUpdateProfileInputSchemaInput
> = z.object({
username: z.string().optional(),
name: z.string().max(FULL_NAME_LENGTH_MAX_LIMIT).optional(),
email: z.string().optional(),
@@ -18,7 +18,11 @@ export type TCreateClientInputSchema = {
enablePkce: boolean;
};
export const ZCreateClientInputSchema: z.ZodType<TCreateClientInputSchema, z.ZodTypeDef, TCreateClientInputSchemaInput> = z.object({
export const ZCreateClientInputSchema: z.ZodType<
TCreateClientInputSchema,
z.ZodTypeDef,
TCreateClientInputSchemaInput
> = z.object({
name: z.string(),
purpose: z.string().min(1),
redirectUri: z.string(),
@@ -1,6 +1,9 @@
import { TRPCError } from "@trpc/server";
import { sendOAuthClientApprovedNotification, sendOAuthClientRejectedNotification } from "@calcom/emails/oauth-email-service";
import {
sendOAuthClientApprovedNotification,
sendOAuthClientRejectedNotification,
} from "@calcom/emails/oauth-email-service";
import { getTranslation } from "@calcom/lib/server/i18n";
import { OAuthClientRepository } from "@calcom/features/oauth/repositories/OAuthClientRepository";
import type { PrismaClient } from "@calcom/prisma";
@@ -31,10 +34,7 @@ type UpdateClientOutput = {
rejectionReason: string | null;
};
const updateClientHandler = async ({
ctx,
input,
}: UpdateClientOptions): Promise<UpdateClientOutput> => {
const updateClientHandler = async ({ ctx, input }: UpdateClientOptions): Promise<UpdateClientOutput> => {
const {
clientId,
status: requestedStatus,
@@ -72,7 +72,10 @@ const updateClientHandler = async ({
}
if (isUpdatingFields && !isAdmin && !isOwner) {
throw new TRPCError({ code: "FORBIDDEN", message: "You do not have permission to update this OAuth client" });
throw new TRPCError({
code: "FORBIDDEN",
message: "You do not have permission to update this OAuth client",
});
}
const shouldTriggerReapprovalForOwnerEdit = triggersReapprovalForOwnerEdit({
@@ -188,7 +191,7 @@ function triggersReapprovalForOwnerEdit(params: {
) {
return true;
}
if (
proposedUpdates.websiteUrl !== undefined &&
toNullableString(proposedUpdates.websiteUrl) !== toNullableString(currentClient.websiteUrl)
@@ -196,7 +199,10 @@ function triggersReapprovalForOwnerEdit(params: {
return true;
}
if (proposedUpdates.redirectUri !== undefined && proposedUpdates.redirectUri !== currentClient.redirectUri) {
if (
proposedUpdates.redirectUri !== undefined &&
proposedUpdates.redirectUri !== currentClient.redirectUri
) {
return true;
}
@@ -2,29 +2,31 @@ import { z } from "zod";
import { OAuthClientStatus } from "@calcom/prisma/enums";
export const ZUpdateClientInputSchema = z.object({
clientId: z.string(),
status: z.nativeEnum(OAuthClientStatus).optional(),
rejectionReason: z.string().min(1).optional(),
name: z.string().min(1).optional(),
purpose: z.string().min(1).optional(),
redirectUri: z.string().url().optional(),
logo: z.preprocess(
(value) => (typeof value === "string" && value.trim() === "" ? null : value),
z.string().nullable().optional()
),
websiteUrl: z.preprocess(
(value) => (typeof value === "string" && value.trim() === "" ? null : value),
z.string().url().nullable().optional()
),
}).superRefine((val, ctx) => {
if (val.status === OAuthClientStatus.REJECTED && !val.rejectionReason) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["rejectionReason"],
message: "Rejection reason is required",
});
}
});
export const ZUpdateClientInputSchema = z
.object({
clientId: z.string(),
status: z.nativeEnum(OAuthClientStatus).optional(),
rejectionReason: z.string().min(1).optional(),
name: z.string().min(1).optional(),
purpose: z.string().min(1).optional(),
redirectUri: z.string().url().optional(),
logo: z.preprocess(
(value) => (typeof value === "string" && value.trim() === "" ? null : value),
z.string().nullable().optional()
),
websiteUrl: z.preprocess(
(value) => (typeof value === "string" && value.trim() === "" ? null : value),
z.string().url().nullable().optional()
),
})
.superRefine((val, ctx) => {
if (val.status === OAuthClientStatus.REJECTED && !val.rejectionReason) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["rejectionReason"],
message: "Rejection reason is required",
});
}
});
export type TUpdateClientInputSchema = z.infer<typeof ZUpdateClientInputSchema>;
@@ -2,7 +2,10 @@ import { lookup } from "node:dns";
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
import { isNotACompanyEmail } from "@calcom/ee/organizations/lib/server/orgCreationUtils";
import { sendAdminOrganizationNotification, sendOrganizationCreationEmail } from "@calcom/emails/organization-email-service";
import {
sendAdminOrganizationNotification,
sendOrganizationCreationEmail,
} from "@calcom/emails/organization-email-service";
import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
import { getOrganizationRepository } from "@calcom/features/ee/organizations/di/OrganizationRepository.container";
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
@@ -45,7 +45,9 @@ describe("deleteWatchlistEntryHandler", () => {
);
vi.mocked(getOrganizationWatchlistOperationsService).mockReturnValue(mockService as never);
vi.mocked(WatchlistRepository).mockImplementation(function() { return mockWatchlistRepo as never; });
vi.mocked(WatchlistRepository).mockImplementation(function () {
return mockWatchlistRepo as never;
});
});
describe("access control", () => {
@@ -2,11 +2,7 @@ import prismock from "@calcom/testing/lib/__mocks__/prisma";
import { intentToCreateOrgHandler } from "./intentToCreateOrg.handler";
import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
import { OrganizationPaymentService } from "@calcom/features/ee/organizations/lib/OrganizationPaymentService";
import {
BillingPeriod,
UserPermissionRole,
CreationSource,
} from "@calcom/prisma/enums";
import { BillingPeriod, UserPermissionRole, CreationSource } from "@calcom/prisma/enums";
import { TRPCError } from "@trpc/server";
import { describe, expect, it, vi, beforeEach } from "vitest";
@@ -40,29 +36,21 @@ vi.mock("@calcom/features/ee/teams/repositories/TeamRepository", () => ({
},
}));
vi.mock(
"@calcom/trpc/server/routers/viewer/organizations/createTeams.handler",
() => ({
createTeamsHandler: vi.fn().mockResolvedValue({}),
})
);
vi.mock("@calcom/trpc/server/routers/viewer/organizations/createTeams.handler", () => ({
createTeamsHandler: vi.fn().mockResolvedValue({}),
}));
vi.mock(
"@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler",
() => ({
inviteMembersWithNoInviterPermissionCheck: vi.fn().mockResolvedValue({}),
})
);
vi.mock("@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler", () => ({
inviteMembersWithNoInviterPermissionCheck: vi.fn().mockResolvedValue({}),
}));
vi.mock("@calcom/lib/server/i18n", () => ({
getTranslation: vi
.fn()
.mockImplementation(async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
}),
getTranslation: vi.fn().mockImplementation(async (locale: string, namespace: string) => {
const t = (key: string) => key;
t.locale = locale;
t.namespace = namespace;
return t;
}),
}));
vi.mock("@calcom/lib/domainManager/organization", () => ({
@@ -116,27 +104,25 @@ describe("intentToCreateOrgHandler", () => {
vi.mocked(OrganizationPaymentService).mockImplementation(function () {
return {
createOrganizationOnboarding: vi
.fn()
.mockImplementation(async (data: any) => {
return await prismock.organizationOnboarding.create({
data: {
id: "onboarding-123",
name: data.name,
slug: data.slug,
orgOwnerEmail: data.orgOwnerEmail,
seats: data.seats ?? 10,
pricePerSeat: data.pricePerSeat ?? 15,
billingPeriod: data.billingPeriod ?? BillingPeriod.MONTHLY,
isComplete: false,
stripeCustomerId: null,
createdById: data.createdByUserId,
teams: data.teams ?? [],
invitedMembers: data.invitedMembers ?? [],
isPlatform: data.isPlatform ?? false,
},
});
}),
createOrganizationOnboarding: vi.fn().mockImplementation(async (data: any) => {
return await prismock.organizationOnboarding.create({
data: {
id: "onboarding-123",
name: data.name,
slug: data.slug,
orgOwnerEmail: data.orgOwnerEmail,
seats: data.seats ?? 10,
pricePerSeat: data.pricePerSeat ?? 15,
billingPeriod: data.billingPeriod ?? BillingPeriod.MONTHLY,
isComplete: false,
stripeCustomerId: null,
createdById: data.createdByUserId,
teams: data.teams ?? [],
invitedMembers: data.invitedMembers ?? [],
isPlatform: data.isPlatform ?? false,
},
});
}),
createPaymentIntent: vi.fn().mockResolvedValue({
checkoutUrl: "https://stripe.com/checkout/session",
organizationOnboarding: {},
@@ -198,25 +184,20 @@ describe("intentToCreateOrgHandler", () => {
organizationOnboardingId: expect.any(String),
checkoutUrl: null,
organizationId: null, // Not created yet - handover flow
handoverUrl: expect.stringContaining(
"/settings/organizations/new/resume?onboardingId="
),
handoverUrl: expect.stringContaining("/settings/organizations/new/resume?onboardingId="),
});
// Verify organization onboarding was created
const organizationOnboarding =
await prismock.organizationOnboarding.findFirst({
where: {
slug: mockInput.slug,
},
});
const organizationOnboarding = await prismock.organizationOnboarding.findFirst({
where: {
slug: mockInput.slug,
},
});
expect(organizationOnboarding).toBeDefined();
expect(organizationOnboarding?.name).toBe(mockInput.name);
expect(organizationOnboarding?.slug).toBe(mockInput.slug);
expect(organizationOnboarding?.orgOwnerEmail).toBe(
mockInput.orgOwnerEmail
);
expect(organizationOnboarding?.orgOwnerEmail).toBe(mockInput.orgOwnerEmail);
});
it("should allow user to create org for themselves", async () => {
@@ -434,25 +415,20 @@ describe("intentToCreateOrgHandler", () => {
organizationOnboardingId: expect.any(String),
checkoutUrl: null,
organizationId: null, // Not created yet - handover flow
handoverUrl: expect.stringContaining(
"/settings/organizations/new/resume?onboardingId="
),
handoverUrl: expect.stringContaining("/settings/organizations/new/resume?onboardingId="),
});
// Verify organization onboarding was created
const organizationOnboarding =
await prismock.organizationOnboarding.findFirst({
where: {
slug: mockInput.slug,
},
});
const organizationOnboarding = await prismock.organizationOnboarding.findFirst({
where: {
slug: mockInput.slug,
},
});
expect(organizationOnboarding).toBeDefined();
expect(organizationOnboarding?.name).toBe(mockInput.name);
expect(organizationOnboarding?.slug).toBe(mockInput.slug);
expect(organizationOnboarding?.orgOwnerEmail).toBe(
mockInput.orgOwnerEmail
);
expect(organizationOnboarding?.orgOwnerEmail).toBe(mockInput.orgOwnerEmail);
});
it("should handle teams and invites in the request", async () => {
@@ -492,20 +468,15 @@ describe("intentToCreateOrgHandler", () => {
expect(result.organizationOnboardingId).toBeDefined();
const organizationOnboarding =
await prismock.organizationOnboarding.findFirst({
where: {
slug: mockInput.slug,
},
});
const organizationOnboarding = await prismock.organizationOnboarding.findFirst({
where: {
slug: mockInput.slug,
},
});
expect(organizationOnboarding).toBeDefined();
expect(organizationOnboarding?.teams).toEqual(
inputWithTeamsAndInvites.teams
);
expect(organizationOnboarding?.invitedMembers).toEqual(
inputWithTeamsAndInvites.invitedMembers
);
expect(organizationOnboarding?.teams).toEqual(inputWithTeamsAndInvites.teams);
expect(organizationOnboarding?.invitedMembers).toEqual(inputWithTeamsAndInvites.invitedMembers);
});
it("should preserve teamName, teamId, and role in invites payload", async () => {
@@ -551,17 +522,14 @@ describe("intentToCreateOrgHandler", () => {
expect(result.organizationOnboardingId).toBeDefined();
const organizationOnboarding =
await prismock.organizationOnboarding.findFirst({
where: {
slug: mockInput.slug,
},
});
const organizationOnboarding = await prismock.organizationOnboarding.findFirst({
where: {
slug: mockInput.slug,
},
});
expect(organizationOnboarding).toBeDefined();
expect(organizationOnboarding?.teams).toEqual(
inputWithTeamsAndInvites.teams
);
expect(organizationOnboarding?.teams).toEqual(inputWithTeamsAndInvites.teams);
// Verify invitedMembers are stored with all fields including teamName, teamId, and role
expect(organizationOnboarding?.invitedMembers).toBeDefined();
@@ -15,9 +15,11 @@ vi.mock("@calcom/prisma", () => ({
// Mock FeaturesRepository
const mockCheckIfTeamHasFeature = vi.fn();
vi.mock("@calcom/features/flags/features.repository", () => ({
FeaturesRepository: vi.fn().mockImplementation(function() { return {
checkIfTeamHasFeature: mockCheckIfTeamHasFeature,
}; }),
FeaturesRepository: vi.fn().mockImplementation(function () {
return {
checkIfTeamHasFeature: mockCheckIfTeamHasFeature,
};
}),
}));
// Mock PBAC permissions
@@ -31,14 +33,16 @@ vi.mock("@calcom/features/pbac/lib/resource-permissions", () => ({
// Mock PermissionCheckService
const mockCheckPermission = vi.fn().mockResolvedValue(true);
vi.mock("@calcom/features/pbac/services/permission-check.service", () => ({
PermissionCheckService: vi.fn().mockImplementation(function() { return {
checkPermission: mockCheckPermission,
}; }),
PermissionCheckService: vi.fn().mockImplementation(function () {
return {
checkPermission: mockCheckPermission,
};
}),
}));
// Mock UserRepository
vi.mock("@calcom/features/users/repositories/UserRepository", () => ({
UserRepository: vi.fn().mockImplementation(function() {
UserRepository: vi.fn().mockImplementation(function () {
return {
enrichUserWithItsProfile: vi.fn().mockImplementation(({ user }) => user),
};
@@ -13,7 +13,7 @@ export type TListMembersSchema = {
limit: number;
offset: number;
searchTerm?: string;
expand?: ("attributes")[];
expand?: "attributes"[];
filters?: { id: string; value: z.infer<typeof ZFilterValue> }[];
oAuthClientId?: string;
};
@@ -93,7 +93,7 @@ const updateOrganizationSettings = async ({
if (input.hasOwnProperty("disableAutofillOnBookingPage")) {
data.disableAutofillOnBookingPage = input.disableAutofillOnBookingPage;
}
// eslint-disable-next-line no-prototype-builtins
if (input.hasOwnProperty("orgAutoJoinOnSignup")) {
data.orgAutoJoinOnSignup = input.orgAutoJoinOnSignup;
@@ -98,43 +98,44 @@ export type TUpdateInputSchema = {
disableAttendeeNewEventEmail?: boolean;
};
export const ZUpdateInputSchema: z.ZodType<TUpdateInputSchema, z.ZodTypeDef, TUpdateInputSchemaInput> = z.object({
name: z.string().optional(),
orgId: z
.string()
.regex(/^\d+$/)
.transform((id) => parseInt(id))
.or(z.number())
.optional(),
bio: z.string().optional(),
logoUrl: ssrfSafeOptionalUrl,
calVideoLogo: ssrfSafeOptionalUrl.transform((v) => v || null),
banner: ssrfSafeOptionalUrl,
slug: z.string().optional(),
hideBranding: z.boolean().optional(),
hideBookATeamMember: z.boolean().optional(),
brandColor: z.string().optional(),
darkBrandColor: z.string().optional(),
theme: z.string().optional().nullable(),
timeZone: timeZoneSchema.optional(),
weekStart: z.string().optional(),
timeFormat: z.number().optional(),
metadata: teamMetadataStrictSchema.unwrap().optional(),
lockEventTypeCreation: z.boolean().optional(),
lockEventTypeCreationOptions: z.enum(["DELETE", "HIDE"]).optional(),
adminGetsNoSlotsNotification: z.boolean().optional(),
allowSEOIndexing: z.boolean().optional(),
orgProfileRedirectsToVerifiedDomain: z.boolean().optional(),
disablePhoneOnlySMSNotifications: z.boolean().optional(),
disableAutofillOnBookingPage: z.boolean().optional(),
orgAutoJoinOnSignup: z.boolean().optional(),
disableAttendeeConfirmationEmail: z.boolean().optional(),
disableAttendeeCancellationEmail: z.boolean().optional(),
disableAttendeeRescheduledEmail: z.boolean().optional(),
disableAttendeeRequestEmail: z.boolean().optional(),
disableAttendeeReassignedEmail: z.boolean().optional(),
disableAttendeeAwaitingPaymentEmail: z.boolean().optional(),
disableAttendeeRescheduleRequestEmail: z.boolean().optional(),
disableAttendeeLocationChangeEmail: z.boolean().optional(),
disableAttendeeNewEventEmail: z.boolean().optional(),
});
export const ZUpdateInputSchema: z.ZodType<TUpdateInputSchema, z.ZodTypeDef, TUpdateInputSchemaInput> =
z.object({
name: z.string().optional(),
orgId: z
.string()
.regex(/^\d+$/)
.transform((id) => parseInt(id))
.or(z.number())
.optional(),
bio: z.string().optional(),
logoUrl: ssrfSafeOptionalUrl,
calVideoLogo: ssrfSafeOptionalUrl.transform((v) => v || null),
banner: ssrfSafeOptionalUrl,
slug: z.string().optional(),
hideBranding: z.boolean().optional(),
hideBookATeamMember: z.boolean().optional(),
brandColor: z.string().optional(),
darkBrandColor: z.string().optional(),
theme: z.string().optional().nullable(),
timeZone: timeZoneSchema.optional(),
weekStart: z.string().optional(),
timeFormat: z.number().optional(),
metadata: teamMetadataStrictSchema.unwrap().optional(),
lockEventTypeCreation: z.boolean().optional(),
lockEventTypeCreationOptions: z.enum(["DELETE", "HIDE"]).optional(),
adminGetsNoSlotsNotification: z.boolean().optional(),
allowSEOIndexing: z.boolean().optional(),
orgProfileRedirectsToVerifiedDomain: z.boolean().optional(),
disablePhoneOnlySMSNotifications: z.boolean().optional(),
disableAutofillOnBookingPage: z.boolean().optional(),
orgAutoJoinOnSignup: z.boolean().optional(),
disableAttendeeConfirmationEmail: z.boolean().optional(),
disableAttendeeCancellationEmail: z.boolean().optional(),
disableAttendeeRescheduledEmail: z.boolean().optional(),
disableAttendeeRequestEmail: z.boolean().optional(),
disableAttendeeReassignedEmail: z.boolean().optional(),
disableAttendeeAwaitingPaymentEmail: z.boolean().optional(),
disableAttendeeRescheduleRequestEmail: z.boolean().optional(),
disableAttendeeLocationChangeEmail: z.boolean().optional(),
disableAttendeeNewEventEmail: z.boolean().optional(),
});
@@ -58,10 +58,7 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions
});
}
if (
message.includes("User ID is required") ||
message.includes("No payment credential found")
) {
if (message.includes("User ID is required") || message.includes("No payment credential found")) {
throw new TRPCError({
code: "BAD_REQUEST",
message,
@@ -50,7 +50,7 @@ async function getEnrichedSerializableForm<
} | null;
metadata: unknown;
} | null;
}
},
>({ form, prisma }: { prisma: PrismaClient; form: TForm }) {
const formWithUserInfoProfile = {
...form,
@@ -13,9 +13,11 @@ const dynamicImportHandler = async () => await import("./reserveSlot.handler");
// The repository instance method is used to check for an existing reservation by someone else.
// To keep this unit test isolated from the database layer, we stub this to always resolve falsey.
vi.mock("@calcom/features/selectedSlots/repositories/PrismaSelectedSlotRepository", () => ({
PrismaSelectedSlotRepository: vi.fn().mockImplementation(function() { return {
findReservedByOthers: vi.fn().mockResolvedValue(null),
}; }),
PrismaSelectedSlotRepository: vi.fn().mockImplementation(function () {
return {
findReservedByOthers: vi.fn().mockResolvedValue(null),
};
}),
}));
// A tiny helper to build a canned handler context with stubbed Prisma methods.
@@ -1280,7 +1280,7 @@ export class AvailableSlotsService {
const restrictionTimezone = eventType.useBookerTimezone
? input.timeZone
: restrictionSchedule.timeZone ?? "UTC";
: (restrictionSchedule.timeZone ?? "UTC");
const eventLength = input.duration || eventType.length;
const restrictionAvailability = restrictionSchedule.availability.map((rule) => ({
@@ -51,7 +51,7 @@ vi.mock("@calcom/features/flags/features.repository");
vi.mock("@calcom/features/membership/repositories/MembershipRepository");
vi.mock("@calcom/features/pbac/services/permission.service", () => {
return {
PermissionService: vi.fn().mockImplementation(function() {
PermissionService: vi.fn().mockImplementation(function () {
return {
validatePermission: vi.fn().mockReturnValue({ isValid: true }),
validatePermissions: vi.fn().mockReturnValue({ isValid: true }),
@@ -62,7 +62,7 @@ vi.mock("@calcom/features/pbac/services/permission.service", () => {
vi.mock("@calcom/features/pbac/services/permission-check.service", () => {
return {
PermissionCheckService: vi.fn().mockImplementation(function() {
PermissionCheckService: vi.fn().mockImplementation(function () {
return {
checkPermission: vi.fn().mockResolvedValue(true),
checkPermissions: vi.fn().mockResolvedValue(true),
@@ -51,18 +51,21 @@ function getOrgConnectionInfoGroupedByUsernameOrEmail({
team: Pick<TeamWithParent, "parentId" | "id">;
isOrg: boolean;
}) {
return uniqueInvitations.reduce((acc, invitation) => {
return {
...acc,
[invitation.usernameOrEmail]: getOrgConnectionInfo({
orgVerified: orgState.orgVerified,
orgAutoAcceptDomain: orgState.autoAcceptEmailDomain,
email: invitation.usernameOrEmail,
team,
isOrg: isOrg,
}),
};
}, {} as Record<string, ReturnType<typeof getOrgConnectionInfo>>);
return uniqueInvitations.reduce(
(acc, invitation) => {
return {
...acc,
[invitation.usernameOrEmail]: getOrgConnectionInfo({
orgVerified: orgState.orgVerified,
orgAutoAcceptDomain: orgState.autoAcceptEmailDomain,
email: invitation.usernameOrEmail,
team,
isOrg: isOrg,
}),
};
},
{} as Record<string, ReturnType<typeof getOrgConnectionInfo>>
);
}
function getInvitationsForNewUsers({
@@ -267,8 +270,8 @@ const inviteMembers = async ({ ctx, input }: InviteMemberOptions) => {
// Check if invitations are blocked due to unpaid invoices
const dueInvoiceService = new DueInvoiceService();
const inviteeEmails = (typeof usernameOrEmail === "string" ? [usernameOrEmail] : usernameOrEmail).map((u) =>
typeof u === "string" ? u : u.email
const inviteeEmails = (typeof usernameOrEmail === "string" ? [usernameOrEmail] : usernameOrEmail).map(
(u) => (typeof u === "string" ? u : u.email)
);
const canInvite = await dueInvoiceService.canInviteToTeam({
teamId: team.id,
@@ -7,7 +7,8 @@ export type TInviteMemberByTokenSchemaInputSchema = {
creationSource: CreationSource;
};
export const ZInviteMemberByTokenSchemaInputSchema: z.ZodType<TInviteMemberByTokenSchemaInputSchema> = z.object({
token: z.string(),
creationSource: z.nativeEnum(CreationSource),
});
export const ZInviteMemberByTokenSchemaInputSchema: z.ZodType<TInviteMemberByTokenSchemaInputSchema> =
z.object({
token: z.string(),
creationSource: z.nativeEnum(CreationSource),
});
@@ -96,10 +96,13 @@ export const listMembersHandler = async ({ ctx, input }: ListMembersHandlerOptio
const roleManager = await RoleManagementFactory.getInstance().createRoleManager(organizationId);
if (roleManager.isPBACEnabled) {
const roles = await roleManager.getTeamRoles(teamId);
customRoles = roles.reduce((acc, role) => {
acc[role.id] = role;
return acc;
}, {} as { [key: string]: { id: string; name: string } });
customRoles = roles.reduce(
(acc, role) => {
acc[role.id] = role;
return acc;
},
{} as { [key: string]: { id: string; name: string } }
);
}
} catch (error) {
// PBAC not enabled or error occurred, continue with traditional roles
@@ -16,7 +16,11 @@ export type TListMembersInputSchema = {
cursor?: number | null;
};
export const ZListMembersInputSchema: z.ZodType<TListMembersInputSchema, z.ZodTypeDef, TListMembersInputSchemaInput> = z.object({
export const ZListMembersInputSchema: z.ZodType<
TListMembersInputSchema,
z.ZodTypeDef,
TListMembersInputSchemaInput
> = z.object({
teamId: z.number(),
limit: z.number().default(10),
searchTerm: z.string().optional(),
@@ -14,7 +14,6 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import type { TGetManagedEventUsersToReassignInputSchema } from "./getManagedEventUsersToReassign.schema";
type GetManagedEventUsersToReassignOptions = {
ctx: {
user: NonNullable<TrpcSessionUser>;
@@ -40,7 +39,6 @@ async function getManagedEventUsersFromDB({
limit?: number;
excludeUserId?: number;
}) {
const eventTypeRepository = new EventTypeRepository(prisma);
const { totalCount, items, hasMore, nextCursor } = await eventTypeRepository.listChildEventTypes({
parentEventTypeId,
@@ -80,7 +78,7 @@ export const getManagedEventUsersToReassign = async ({
const eventTypeRepository = new EventTypeRepository(prisma);
const booking = await bookingRepository.findByIdForTargetEventTypeSearch(bookingId);
if (!booking) {
throw new Error(`Booking ${bookingId} not found`);
}
@@ -90,7 +88,7 @@ export const getManagedEventUsersToReassign = async ({
}
const childEventType = await eventTypeRepository.findByIdWithParent(booking.eventTypeId);
if (!childEventType) {
throw new Error(`Event type ${booking.eventTypeId} not found`);
}
@@ -152,4 +150,3 @@ export const getManagedEventUsersToReassign = async ({
};
export default getManagedEventUsersToReassign;
@@ -10,4 +10,3 @@ export const ZGetManagedEventUsersToReassignInputSchema = z.object({
export type TGetManagedEventUsersToReassignInputSchema = z.infer<
typeof ZGetManagedEventUsersToReassignInputSchema
>;
@@ -42,4 +42,3 @@ export const managedEventManualReassignHandler = async ({
};
export default managedEventManualReassignHandler;
@@ -12,4 +12,3 @@ export const ZManagedEventManualReassignInputSchema: z.ZodType<TManagedEventManu
teamMemberId: z.number().int(),
reassignReason: z.string().optional(),
});
@@ -42,4 +42,3 @@ export const managedEventReassignHandler = async ({ ctx, input }: ManagedEventRe
};
export default managedEventReassignHandler;
@@ -7,4 +7,3 @@ export type TManagedEventReassignInputSchema = {
export const ZManagedEventReassignInputSchema: z.ZodType<TManagedEventReassignInputSchema> = z.object({
bookingId: z.number().int(),
});
@@ -8,6 +8,7 @@ export type TPublishInputSchema = {
teamId: number;
};
export const ZPublishInputSchema: z.ZodType<TPublishInputSchema, z.ZodTypeDef, TPublishInputRawSchema> = z.object({
teamId: z.coerce.number(),
});
export const ZPublishInputSchema: z.ZodType<TPublishInputSchema, z.ZodTypeDef, TPublishInputRawSchema> =
z.object({
teamId: z.coerce.number(),
});
@@ -130,9 +130,7 @@ describe("removeHostsFromEventTypesHandler", () => {
});
// Mock that only userId 101 is a team member, 102 is not
(MembershipRepository.findAcceptedMembershipsByUserIdsInTeam as any).mockResolvedValue([
{ userId: 101 },
]);
(MembershipRepository.findAcceptedMembershipsByUserIdsInTeam as any).mockResolvedValue([{ userId: 101 }]);
const mockDeleteResult = { count: 1 };
(prisma.host.deleteMany as any).mockResolvedValue(mockDeleteResult);
@@ -284,9 +282,7 @@ describe("removeHostsFromEventTypesHandler", () => {
});
// User 999 is a valid team member
(MembershipRepository.findAcceptedMembershipsByUserIdsInTeam as any).mockResolvedValue([
{ userId: 999 },
]);
(MembershipRepository.findAcceptedMembershipsByUserIdsInTeam as any).mockResolvedValue([{ userId: 999 }]);
// But they're not a host on any of the event types
const mockDeleteResult = { count: 0 };
@@ -14,7 +14,11 @@ export type TRemoveMemberInputSchema = {
isOrg: boolean;
};
export const ZRemoveMemberInputSchema: z.ZodType<TRemoveMemberInputSchema, z.ZodTypeDef, TRemoveMemberInputSchemaInput> = z.object({
export const ZRemoveMemberInputSchema: z.ZodType<
TRemoveMemberInputSchema,
z.ZodTypeDef,
TRemoveMemberInputSchemaInput
> = z.object({
teamIds: z.array(z.number()),
memberIds: z.array(z.number()),
isOrg: z.boolean().default(false),
@@ -16,7 +16,9 @@ export class RemoveMemberServiceFactory {
const isPBACEnabled = await featuresRepository.checkIfTeamHasFeature(teamId, "pbac");
const teamRepository = new TeamRepository(prisma);
const service = isPBACEnabled ? new PBACRemoveMemberService() : new LegacyRemoveMemberService(teamRepository);
const service = isPBACEnabled
? new PBACRemoveMemberService()
: new LegacyRemoveMemberService(teamRepository);
return service;
}
@@ -36,7 +36,9 @@ describe("PBACRemoveMemberService", () => {
getTeamIdsWithPermission: vi.fn(),
};
vi.mocked(PermissionCheckService).mockImplementation(function() { return mockPermissionCheckService as any; });
vi.mocked(PermissionCheckService).mockImplementation(function () {
return mockPermissionCheckService as any;
});
service = new PBACRemoveMemberService();
});
@@ -22,7 +22,9 @@ describe("RemoveMemberServiceFactory", () => {
checkIfTeamHasFeature: vi.fn(),
};
vi.mocked(FeaturesRepository).mockImplementation(function() { return mockFeaturesRepository as any; });
vi.mocked(FeaturesRepository).mockImplementation(function () {
return mockFeaturesRepository as any;
});
});
describe("Service Creation", () => {
@@ -16,7 +16,11 @@ export type TResendInvitationInputSchema = {
isOrg: boolean;
};
export const ZResendInvitationInputSchema: z.ZodType<TResendInvitationInputSchema, z.ZodTypeDef, TResendInvitationInputSchemaInput> = z.object({
export const ZResendInvitationInputSchema: z.ZodType<
TResendInvitationInputSchema,
z.ZodTypeDef,
TResendInvitationInputSchemaInput
> = z.object({
teamId: z.number(),
email: emailSchema,
language: z.string(),
@@ -6,8 +6,10 @@ export type TRoundRobinManualReassignInputSchema = {
reassignReason?: string;
};
export const ZRoundRobinManualReassignInputSchema: z.ZodType<TRoundRobinManualReassignInputSchema> = z.object({
bookingId: z.number(),
teamMemberId: z.number(),
reassignReason: z.string().optional(),
});
export const ZRoundRobinManualReassignInputSchema: z.ZodType<TRoundRobinManualReassignInputSchema> = z.object(
{
bookingId: z.number(),
teamMemberId: z.number(),
reassignReason: z.string().optional(),
}
);
@@ -26,15 +26,19 @@ vi.mock("@calcom/prisma", () => ({
}));
vi.mock("@calcom/features/pbac/services/permission-check.service", () => ({
PermissionCheckService: vi.fn().mockImplementation(function() { return {
checkPermission: vi.fn(),
}; }),
PermissionCheckService: vi.fn().mockImplementation(function () {
return {
checkPermission: vi.fn(),
};
}),
}));
vi.mock("@calcom/features/ee/teams/repositories/TeamRepository", () => ({
TeamRepository: vi.fn().mockImplementation(function() { return {
isSlugAvailableForUpdate: vi.fn().mockResolvedValue(true),
}; }),
TeamRepository: vi.fn().mockImplementation(function () {
return {
isSlugAvailableForUpdate: vi.fn().mockResolvedValue(true),
};
}),
}));
vi.mock("@calcom/lib/server/avatar", () => ({
@@ -9,13 +9,14 @@ export type TUpdateInternalNotesPresetsInputSchema = {
}[];
};
export const ZUpdateInternalNotesPresetsInputSchema: z.ZodType<TUpdateInternalNotesPresetsInputSchema> = z.object({
teamId: z.number(),
presets: z.array(
z.object({
id: z.number(),
name: z.string(),
cancellationReason: z.string().optional(),
})
),
});
export const ZUpdateInternalNotesPresetsInputSchema: z.ZodType<TUpdateInternalNotesPresetsInputSchema> =
z.object({
teamId: z.number(),
presets: z.array(
z.object({
id: z.number(),
name: z.string(),
cancellationReason: z.string().optional(),
})
),
});
@@ -28,9 +28,11 @@ vi.mock("@calcom/prisma", () => ({
const mockCheckPermission = vi.fn();
vi.mock("@calcom/features/pbac/services/permission-check.service", () => ({
PermissionCheckService: vi.fn().mockImplementation(function() { return {
checkPermission: mockCheckPermission,
}; }),
PermissionCheckService: vi.fn().mockImplementation(function () {
return {
checkPermission: mockCheckPermission,
};
}),
}));
vi.mock("../../../procedures/authedProcedure", () => ({
@@ -298,4 +298,4 @@ describe("sendVerificationCodeHandler", () => {
expect(sendVerificationCode).toHaveBeenCalledWith(mockInput.phoneNumber);
});
});
});
});