* chore: Upgrade prisma to 6.7.0 * Build fixes * type fixes Signed-off-by: Omar López <zomars@me.com> * Update schema.prisma * Patching * Revert "Update schema.prisma" This reverts commit 47d8618bf89ef4d007b30084df766f17281e21a1. * Revert "Patching" This reverts commit a1d2e3040e71690a44d4324db95d73b4d68c6adb. * Revert schema changes Signed-off-by: Omar López <zomars@me.com> * WIP Signed-off-by: Omar López <zomars@me.com> * Update getPublicEvent.ts * Update imports Signed-off-by: Omar López <zomars@me.com> * Update gitignore Signed-off-by: Omar López <zomars@me.com> * update remaining imports Signed-off-by: Omar López <zomars@me.com> * Delete .cursor/config.json * Discard changes to packages/features/eventtypes/lib/getPublicEvent.ts * Update _get.ts * Update user.ts * Update .gitignore * update * Update WorkflowStepContainer.tsx * Update next-auth-custom-adapter.ts * Update getPublicEvent.ts * Update workflow.ts * Update next-auth-custom-adapter.ts * Update next-auth-options.ts * Update bookingScenario.ts * fix missing imports * upgrades prismock Signed-off-by: Omar López <zomars@me.com> * patches prismock Signed-off-by: Omar López <zomars@me.com> * Update reschedule.test.ts * Update prisma.ts * patch prismock Signed-off-by: Omar López <zomars@me.com> * fix enums imports Signed-off-by: Omar López <zomars@me.com> * Revert "Update prisma.ts" This reverts commit 64edcf8db54171ff4456c209d563b5d431d99619. * Revert "patch prismock" This reverts commit e95819113dc9d88e7130947aa120cd42710977c8. * fix patch * Fix test that overrun the boundary, it shouldn't test too much * Move prisma import to changeSMSLockState * Bring back broken test without illegal imports * Merge with main and fix filter hosts by same round robin host * Fixed buildDryRunBooking fn tests * Fix and move ooo create or update handler test * Fix packages/features/eventtypes/lib/isCurrentlyAvailable.test.ts * Fix packages/trpc/server/routers/viewer/organizations/listMembers.handler.test.ts * Mock @calcom/prisma * Fix: verify-email.test.ts * fix: Moved WebhookService test and fixed default import mock * Fix: Added missing prisma mock, handleNewBooking uses that of course * We're not testing createContext here * fix: Prisma mock fix for listMembers.test.ts * More fixes to broken testcases * Forgot to remove borked test * Prevent the need to mock a lot of dependencies by moving out buildBaseWhereCondition to its own file * Temporarily skip getCalendarEvents, needs a rewrite * Fix: turns out you can access protected in testcases * fix further mocks * Added packages/features/insights/server/buildBaseWhereCondition.ts, types * Always great to have a mock and then not use it * And one less again. * fix: confirm.handler.test, didn't mock prisma * fix: Address minor nit by @eunjae & fix ImpersonationProvider test * Updated isPrismaAvailableCheck that doesn't crash on import * fix: Get Prisma directly from the client, it usually involves the Validator and does not need 'local' inclusion * Add zod-prisma-types without the generator enabled (commented out) * Uncomment and see what happens * Change method of import as imports did not work in Input Schemas * Remove custom 'zod' booking model, it does not belong with Prisma * Fix all other global Model imports * Rewrite most schema includes AND remove barrel file * Add bookingCreateBodySchema to features/bookings * Flurry of type fixes for compatibility with new zod gen * Refactor out the custom prisma type createEventTypeInput * Work around nullable eventTypeLocations * HandlePayment type fix * More fixes, final fix remaining is CompleteEventType * Should fix a bunch more booking related type errors * Missed one * Some props missing from BookingCreateBodySchema * Fix location type in handleChildrenEventTypes * Little bit hacky imo but it works * Final type error \o/ * Forgot to include Prisma * Do not include zod-utils in booker/types * Oops, was already including Booker/types * Fix membership type, also disallow updating createdAt/updatedAt, make part of patch/post * Fix api v1 type errors * Fix EventTypeDescription typings * Remove getParserWithGeneric, use userBodySchema with UserSchema * use centralized timeZoneSchema * Implement feedback by @zomars * Couple of WIP pushes * Fix tests * Type fixes in `handleChildrenEventTypes` test * Try and parse metadata before use * Change zod-prisma-types configuration for optimal performance * Fix prisma validator error in `prisma/selects/credential` * Disable seperate relations model, hits a bug * Import absolute - this makes rollup work in @platform/libraries * Attempt at removing resolutions override * Refactor using `Prisma.validator` to `satisfies` * Build atoms using @calcom/prisma/client * Build atoms using @calcom/prisma/client * fixes * Update eventTypeSelect.ts * Adjust `eventTypeMetaDataSchemaWithUntypedApps` from `unknown` to `record(any)` * `EventTypeDescription` rely on `descriptionAsSafeHTML` instead of `description` * Add `seatsPerTimeSlot` to event type public select * Fix typing in `users-public-view` getServerSide props * Add missing `schedulingType` to prop * chore: bump platform libraries * Function return type is illegal, not sure how this passed eslint (#21567) * Merged with main * Update updateTokenObject.ts * Update handleResponse.ts * Update index.ts * Update handleChildrenEventTypes.ts * Update booking-idempotency-key.ts * Update WebhookService.test.ts * Update events.test.ts * Update queued-response.test.ts * Update events.test.ts * Update getRoutedUrl.test.ts * fix: type checks Signed-off-by: Omar López <zomars@me.com> * fixes Signed-off-by: Omar López <zomars@me.com> * chore: bump platform libraries * Update yarn.lock * more fixes Signed-off-by: Omar López <zomars@me.com> * fixes Signed-off-by: Omar López <zomars@me.com> * biuld fixes * chore: bump platform libraries * Update conferencing.repository.ts * Update conferencing.repository.ts * Update getCalendarsEvents.test.ts * Update vite.config.js * chore: bump platform libraries * Update users.ts * Discard changes to docs/api-reference/v2/openapi.json * Update vite.config.ts * updated platform libraries * Update get.handler.test.ts * Update get.handler.test.ts * Update schema.prisma * Discard changes to docs/api-reference/v2/openapi.json * Update next-auth-custom-adapter.ts * Update team.ts * Flurry of type fixes * Fix majority of insight related type errors * Type fixes for unlink of account * Make user nullable again * Fixed a bunch of unit tests and one type error * Attempted mock fix * Attempted fix for Attribute type * Ensure default import becomes prisma, but not direct usage * Import default as prisma in prisma.module * Add attributeOption to attribute type * Fix calcom/prisma mock * Refactor Prisma client imports to @calcom/prisma/client Updated all imports from '@prisma/client' to '@calcom/prisma/client' across tests and repository files for consistency and to use the correct Prisma client package. This change improves maintainability and ensures the correct client is referenced throughout the codebase. * Undo removal of max-warnings=0 to get main to merge * Remove unit tests for e2e fixtures, provide new prisma mock * Mock @calcom/prisma in event manager * Mock @calcom/prisma in event manager * Add correct format even with --no-verify * Mock prisma in CalendarManager * Add mock for permission-check.service * Better injection in PrismaApiKeyRepository imports * More mock fixes :) * Fix listMembers.handler.test * Fix User import * Appropriately adjust all types to be imported as types, there were a lot of types imported as normal deps * Why was this a thing? * Strictly speaking; Not using prismock anymore * Ditched patch file for prismock * Fix output.service.ts platform type imports, need concrete for plainToClass * Better typing and tests for unlinkConnectedAccount.handler * Small type fix * Disable calendar cache tests as they are dependent on prismock * chore: bump platform lib * getRoutedUrl test remove of unused import * Extract select to external const on getEventTypesFromDB * Direct select of userSelect from selects/user * fix type error from merging 23653 * Fixed integration tests by removing hardcoded values that were possible due to mocking, but as its now directly hitting the db no longer * fix: vite config atoms prisma client type location * revert: example app prisma client * revert: example app prisma client * bump platform libs * fix: use class instead of type for DI of PlatformBookingsService * update platform libs * remove unused variable * chore: generate prisma client for api v2 * fix: api v2 e2e * fix: atoms e2e * fix: atoms e2e * fix: atoms e2e * fix: api v2 e2e * fix: tsconfig apiv2 enums * publish libraries * Simplify check for existence teamId --------- Signed-off-by: Omar López <zomars@me.com> Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: cal.com <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Benny Joo <sldisek783@gmail.com>
782 lines
24 KiB
TypeScript
782 lines
24 KiB
TypeScript
import type { UnitTypeLongPlural } from "dayjs";
|
|
import type { TFunction } from "i18next";
|
|
import z, { ZodNullable, ZodObject, ZodOptional } from "zod";
|
|
import type {
|
|
AnyZodObject,
|
|
objectInputType,
|
|
objectOutputType,
|
|
ZodNullableDef,
|
|
ZodOptionalDef,
|
|
ZodRawShape,
|
|
ZodTypeAny,
|
|
} from "zod";
|
|
|
|
import { appDataSchemas } from "@calcom/app-store/apps.schemas.generated";
|
|
import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid";
|
|
import type { FieldType as FormBuilderFieldType } from "@calcom/features/form-builder/schema";
|
|
import { fieldsSchema as formBuilderFieldsSchema } from "@calcom/features/form-builder/schema";
|
|
import { emailSchema as emailRegexSchema, emailRegex } from "@calcom/lib/emailSchema";
|
|
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
|
import { zodAttributesQueryValue } from "@calcom/lib/raqb/zod";
|
|
import { slugify } from "@calcom/lib/slugify";
|
|
import { EventTypeCustomInputType } from "@calcom/prisma/enums";
|
|
|
|
import type { Prisma } from "./client";
|
|
|
|
// Let's not import 118kb just to get an enum
|
|
export enum Frequency {
|
|
YEARLY = 0,
|
|
MONTHLY = 1,
|
|
WEEKLY = 2,
|
|
DAILY = 3,
|
|
HOURLY = 4,
|
|
MINUTELY = 5,
|
|
SECONDLY = 6,
|
|
}
|
|
|
|
export enum BookerLayouts {
|
|
MONTH_VIEW = "month_view",
|
|
WEEK_VIEW = "week_view",
|
|
COLUMN_VIEW = "column_view",
|
|
}
|
|
|
|
export const bookerLayoutOptions = [
|
|
BookerLayouts.MONTH_VIEW,
|
|
BookerLayouts.WEEK_VIEW,
|
|
BookerLayouts.COLUMN_VIEW,
|
|
];
|
|
|
|
const layoutOptions = z.union([
|
|
z.literal(bookerLayoutOptions[0]),
|
|
z.literal(bookerLayoutOptions[1]),
|
|
z.literal(bookerLayoutOptions[2]),
|
|
]);
|
|
|
|
export const bookerLayouts = z
|
|
.object({
|
|
enabledLayouts: z.array(layoutOptions),
|
|
defaultLayout: layoutOptions,
|
|
})
|
|
.nullable();
|
|
|
|
export const orgOnboardingInvitedMembersSchema = z.array(
|
|
z.object({ email: z.string().email(), name: z.string().optional() })
|
|
);
|
|
|
|
export const orgOnboardingTeamsSchema = z.array(
|
|
z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
isBeingMigrated: z.boolean(),
|
|
// "slug" is null for new teams
|
|
slug: z.string().nullable(),
|
|
})
|
|
);
|
|
|
|
export const defaultBookerLayoutSettings = {
|
|
defaultLayout: BookerLayouts.MONTH_VIEW,
|
|
// if the user has no explicit layouts set (not in user profile and not in event settings), all layouts are enabled.
|
|
enabledLayouts: bookerLayoutOptions,
|
|
};
|
|
|
|
export type BookerLayoutSettings = z.infer<typeof bookerLayouts>;
|
|
|
|
export const RequiresConfirmationThresholdUnits: z.ZodType<UnitTypeLongPlural> = z.enum(["hours", "minutes"]);
|
|
|
|
export const EventTypeAppMetadataSchema = z.object(appDataSchemas).partial();
|
|
export const eventTypeAppMetadataOptionalSchema = EventTypeAppMetadataSchema.optional();
|
|
|
|
const _eventTypeMetaDataSchemaWithoutApps = z.object({
|
|
smartContractAddress: z.string().optional(),
|
|
blockchainId: z.number().optional(),
|
|
multipleDuration: z.number().array().optional(),
|
|
giphyThankYouPage: z.string().optional(),
|
|
additionalNotesRequired: z.boolean().optional(),
|
|
disableSuccessPage: z.boolean().optional(),
|
|
disableStandardEmails: z
|
|
.object({
|
|
all: z
|
|
.object({
|
|
host: z.boolean().optional(),
|
|
attendee: z.boolean().optional(),
|
|
})
|
|
.optional(),
|
|
confirmation: z
|
|
.object({
|
|
host: z.boolean().optional(),
|
|
attendee: z.boolean().optional(),
|
|
})
|
|
.optional(),
|
|
})
|
|
.optional(),
|
|
managedEventConfig: z
|
|
.object({
|
|
unlockedFields: z.custom<{ [k in keyof Omit<Prisma.EventTypeSelect, "id">]: true }>().optional(),
|
|
})
|
|
.optional(),
|
|
requiresConfirmationThreshold: z
|
|
.object({
|
|
time: z.number(),
|
|
unit: RequiresConfirmationThresholdUnits,
|
|
})
|
|
.optional(),
|
|
config: z
|
|
.object({
|
|
useHostSchedulesForTeamEvent: z.boolean().optional(),
|
|
})
|
|
.optional(),
|
|
bookerLayouts: bookerLayouts.optional(),
|
|
});
|
|
|
|
export const eventTypeMetaDataSchemaWithUntypedApps = _eventTypeMetaDataSchemaWithoutApps.merge(
|
|
z.object({
|
|
apps: z.record(z.string(), z.any()).optional(),
|
|
})
|
|
);
|
|
|
|
export const EventTypeMetaDataSchema = eventTypeMetaDataSchemaWithUntypedApps.nullable();
|
|
export const eventTypeMetaDataSchemaWithoutApps = _eventTypeMetaDataSchemaWithoutApps.nullable();
|
|
export const eventTypeMetaDataSchemaWithTypedApps = _eventTypeMetaDataSchemaWithoutApps
|
|
.merge(
|
|
z.object({
|
|
apps: eventTypeAppMetadataOptionalSchema,
|
|
})
|
|
)
|
|
.nullable();
|
|
|
|
export type EventTypeMetadata = z.infer<typeof EventTypeMetaDataSchema>;
|
|
|
|
export const eventTypeBookingFields = formBuilderFieldsSchema;
|
|
export const BookingFieldTypeEnum = eventTypeBookingFields.element.shape.type.Enum;
|
|
export type BookingFieldType = FormBuilderFieldType;
|
|
|
|
// Validation of user added bookingFields' responses happen using `getBookingResponsesSchema` which requires `eventType`.
|
|
// So it is a dynamic validation and thus entire validation can't exist here
|
|
// Note that this validation runs to validate prefill params as well, so it should consider that partial values can be there. e.g. `name` might be empty string
|
|
export const bookingResponses = z
|
|
.object({
|
|
email: z.string(),
|
|
attendeePhoneNumber: z.string().optional(),
|
|
//TODO: Why don't we move name out of bookingResponses and let it be handled like user fields?
|
|
name: z.union([
|
|
z.string(),
|
|
z.object({
|
|
firstName: z.string(),
|
|
lastName: z.string().optional(),
|
|
}),
|
|
]),
|
|
guests: z.array(z.string()).optional(),
|
|
notes: z.string().optional(),
|
|
location: z
|
|
.object({
|
|
optionValue: z.string(),
|
|
value: z.string(),
|
|
})
|
|
.optional(),
|
|
smsReminderNumber: z.string().optional(),
|
|
rescheduleReason: z.string().optional(),
|
|
})
|
|
.nullable();
|
|
|
|
export type BookingResponses = z.infer<typeof bookingResponses>;
|
|
|
|
export const eventTypeLocations = z.array(
|
|
z.object({
|
|
// TODO: Couldn't find a way to make it a union of types from App Store locations
|
|
// Creating a dynamic union by iterating over the object doesn't seem to make TS happy
|
|
type: z.string(),
|
|
address: z.string().optional(),
|
|
link: z.string().url().optional(),
|
|
displayLocationPublicly: z.boolean().optional(),
|
|
hostPhoneNumber: z.string().optional(),
|
|
credentialId: z.number().optional(),
|
|
teamName: z.string().optional(),
|
|
customLabel: z.string().optional(),
|
|
})
|
|
);
|
|
|
|
// Matching RRule.Options: rrule/dist/esm/src/types.d.ts
|
|
export const recurringEventType = z
|
|
.object({
|
|
dtstart: z.date().optional(),
|
|
interval: z.number(),
|
|
count: z.number(),
|
|
freq: z.nativeEnum(Frequency),
|
|
until: z.date().optional(),
|
|
tzid: z.string().optional(),
|
|
})
|
|
.nullable();
|
|
|
|
// dayjs iso parsing is very buggy - cant use :( - turns ISO string into Date object
|
|
export const iso8601 = z.string().transform((val, ctx) => {
|
|
const time = Date.parse(val);
|
|
if (!time) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: "Invalid ISO Date",
|
|
});
|
|
}
|
|
const d = new Date();
|
|
d.setTime(time);
|
|
return d;
|
|
});
|
|
|
|
export const eventTypeColor = z
|
|
.object({
|
|
lightEventTypeColor: z.string(),
|
|
darkEventTypeColor: z.string(),
|
|
})
|
|
.nullable();
|
|
|
|
export type IntervalLimitsType = IntervalLimit | null;
|
|
|
|
export { intervalLimitsType } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
|
|
|
export const eventTypeSlug = z
|
|
.string()
|
|
.trim()
|
|
.transform((val) => slugify(val))
|
|
.refine((val) => val.length >= 1, {
|
|
message: "Please enter at least one character",
|
|
});
|
|
|
|
export const stringToDate = z.string().transform((a) => new Date(a));
|
|
|
|
export const stringOrNumber = z.union([
|
|
z.string().transform((v, ctx) => {
|
|
const parsed = parseInt(v);
|
|
if (isNaN(parsed)) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: "Not a number",
|
|
});
|
|
}
|
|
return parsed;
|
|
}),
|
|
z.number().int(),
|
|
]);
|
|
|
|
export const requiredCustomInputSchema = z.union([
|
|
// string must be given & nonempty
|
|
z.string().trim().min(1),
|
|
// boolean must be true if set.
|
|
z.boolean().refine((v) => v === true),
|
|
]);
|
|
|
|
const PlatformClientParamsSchema = z.object({
|
|
platformClientId: z.string().optional(),
|
|
platformRescheduleUrl: z.string().nullable().optional(),
|
|
platformCancelUrl: z.string().nullable().optional(),
|
|
platformBookingUrl: z.string().nullable().optional(),
|
|
platformBookingLocation: z.string().optional(),
|
|
areCalendarEventsEnabled: z.boolean().optional(),
|
|
});
|
|
|
|
export type PlatformClientParams = z.infer<typeof PlatformClientParamsSchema>;
|
|
|
|
export const bookingConfirmPatchBodySchema = z.object({
|
|
bookingId: z.number(),
|
|
confirmed: z.boolean(),
|
|
recurringEventId: z.string().optional(),
|
|
reason: z.string().optional(),
|
|
emailsEnabled: z.boolean().default(true),
|
|
platformClientParams: PlatformClientParamsSchema.optional(),
|
|
});
|
|
|
|
export const bookingCancelSchema = z.object({
|
|
id: z.number().optional(),
|
|
uid: z.string().optional(),
|
|
// note(Lauris): allRemainingBookings will cancel all bookings that have start time greater than this moment.
|
|
allRemainingBookings: z.boolean().optional(),
|
|
// note(Lauris): cancelSubsequentBookings will cancel all bookings after one specified by id or uid.
|
|
cancelSubsequentBookings: z.boolean().optional(),
|
|
cancellationReason: z.string().optional(),
|
|
seatReferenceUid: z.string().optional(),
|
|
cancelledBy: z.string().email({ message: "Invalid email" }).optional(),
|
|
internalNote: z
|
|
.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
cancellationReason: z.string().optional().nullable(),
|
|
})
|
|
.optional()
|
|
.nullable(),
|
|
});
|
|
|
|
export const bookingCancelAttendeeSeatSchema = z.object({
|
|
seatReferenceUid: z.string(),
|
|
});
|
|
|
|
export const bookingCancelInput = bookingCancelSchema.refine(
|
|
(data) => !!data.id || !!data.uid,
|
|
"At least one of the following required: 'id', 'uid'."
|
|
);
|
|
|
|
export const bookingCancelWithCsrfSchema = bookingCancelSchema
|
|
.extend({
|
|
csrfToken: z.string().length(64, "Invalid CSRF token"),
|
|
})
|
|
.refine((data) => !!data.id || !!data.uid, "At least one of the following required: 'id', 'uid'.");
|
|
|
|
export const vitalSettingsUpdateSchema = z.object({
|
|
connected: z.boolean().optional(),
|
|
selectedParam: z.string().optional(),
|
|
sleepValue: z.number().optional(),
|
|
});
|
|
|
|
export const createdEventSchema = z
|
|
.object({
|
|
id: z.string(),
|
|
thirdPartyRecurringEventId: z.string(),
|
|
password: z.union([z.string(), z.undefined()]),
|
|
onlineMeetingUrl: z.string().nullable(),
|
|
iCalUID: z.string().optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
const schemaDefaultConferencingApp = z.object({
|
|
appSlug: z.string().default("daily-video").optional(),
|
|
appLink: z.string().optional(),
|
|
});
|
|
|
|
export const userMetadata = z
|
|
.object({
|
|
proPaidForByTeamId: z.number().optional(),
|
|
stripeCustomerId: z.string().optional(),
|
|
vitalSettings: vitalSettingsUpdateSchema.optional(),
|
|
isPremium: z.boolean().optional(),
|
|
sessionTimeout: z.number().optional(), // Minutes
|
|
defaultConferencingApp: schemaDefaultConferencingApp.optional(),
|
|
defaultBookerLayouts: bookerLayouts.optional(),
|
|
emailChangeWaitingForVerification: z
|
|
.string()
|
|
.transform((data) => data.toLowerCase())
|
|
.optional(),
|
|
migratedToOrgFrom: z
|
|
.object({
|
|
username: z.string().or(z.null()).optional(),
|
|
lastMigrationTime: z.string().optional(),
|
|
reverted: z.boolean().optional(),
|
|
revertTime: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
})
|
|
.nullable();
|
|
|
|
export type DefaultConferencingApp = z.infer<typeof schemaDefaultConferencingApp>;
|
|
|
|
export const orgSettingsSchema = z
|
|
.object({
|
|
isOrganizationVerified: z.boolean().optional(),
|
|
isOrganizationConfigured: z.boolean().optional(),
|
|
isAdminReviewed: z.boolean().optional(),
|
|
orgAutoAcceptEmail: z.string().optional(),
|
|
isAdminAPIEnabled: z.boolean().optional(),
|
|
})
|
|
.nullable();
|
|
export type userMetadataType = z.infer<typeof userMetadata>;
|
|
|
|
export enum BillingPeriod {
|
|
MONTHLY = "MONTHLY",
|
|
ANNUALLY = "ANNUALLY",
|
|
}
|
|
|
|
const baseTeamMetadataSchema = z.object({
|
|
defaultConferencingApp: schemaDefaultConferencingApp.optional(),
|
|
requestedSlug: z.string().or(z.null()),
|
|
paymentId: z.string(),
|
|
subscriptionId: z.string().nullable(),
|
|
subscriptionItemId: z.string().nullable(),
|
|
orgSeats: z.number().nullable(),
|
|
orgPricePerSeat: z.number().nullable(),
|
|
migratedToOrgFrom: z
|
|
.object({
|
|
teamSlug: z.string().or(z.null()).optional(),
|
|
lastMigrationTime: z.string().optional(),
|
|
reverted: z.boolean().optional(),
|
|
lastRevertTime: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
billingPeriod: z.nativeEnum(BillingPeriod).optional(),
|
|
});
|
|
|
|
export const teamMetadataSchema = baseTeamMetadataSchema.partial().nullable();
|
|
|
|
export const teamMetadataStrictSchema = baseTeamMetadataSchema
|
|
.extend({
|
|
subscriptionId: z
|
|
.string()
|
|
.refine((val) => val.startsWith("sub_"), {
|
|
message: "subscriptionId must start with 'sub_'",
|
|
})
|
|
.nullable(),
|
|
subscriptionItemId: z
|
|
.string()
|
|
.refine((val) => val.startsWith("si_"), {
|
|
message: "subscriptionItemId must start with 'si_'",
|
|
})
|
|
.nullable(),
|
|
})
|
|
.partial()
|
|
.nullable();
|
|
|
|
export const bookingMetadataSchema = z
|
|
.object({
|
|
videoCallUrl: z.string().optional(),
|
|
})
|
|
.and(z.record(z.string()))
|
|
.nullable()
|
|
.describe("BookingMetadata");
|
|
|
|
export const customInputOptionSchema = z.array(
|
|
z.object({
|
|
label: z.string(),
|
|
type: z.string(),
|
|
})
|
|
);
|
|
|
|
export const customInputSchema = z.object({
|
|
id: z.number(),
|
|
eventTypeId: z.number(),
|
|
label: z.string(),
|
|
type: z.nativeEnum(EventTypeCustomInputType),
|
|
options: customInputOptionSchema.optional().nullable(),
|
|
required: z.boolean(),
|
|
placeholder: z.string(),
|
|
hasToBeCreated: z.boolean().optional(),
|
|
});
|
|
|
|
export type CustomInputSchema = z.infer<typeof customInputSchema>;
|
|
|
|
export const recordingItemSchema = z
|
|
.object({
|
|
id: z.string(),
|
|
room_name: z.string(),
|
|
start_ts: z.number(),
|
|
status: z.string(),
|
|
max_participants: z.number().optional(),
|
|
duration: z.number(),
|
|
share_token: z.string(),
|
|
})
|
|
.passthrough();
|
|
|
|
export const recordingItemsSchema = z.array(recordingItemSchema);
|
|
|
|
export type RecordingItemSchema = z.infer<typeof recordingItemSchema>;
|
|
|
|
export const getRecordingsResponseSchema = z.union([
|
|
z.object({
|
|
total_count: z.number(),
|
|
data: recordingItemsSchema,
|
|
}),
|
|
z.object({}),
|
|
]);
|
|
|
|
export type GetRecordingsResponseSchema = z.infer<typeof getRecordingsResponseSchema>;
|
|
|
|
/**
|
|
* Ensures that it is a valid HTTP URL
|
|
* It automatically avoids
|
|
* - XSS attempts through javascript:alert('hi')
|
|
* - mailto: links
|
|
*/
|
|
export const successRedirectUrl = z
|
|
.union([
|
|
z.literal(""),
|
|
z
|
|
.string()
|
|
.url()
|
|
.regex(/^http(s)?:\/\/.*/),
|
|
])
|
|
.optional();
|
|
|
|
export const RoutingFormSettings = z
|
|
.object({
|
|
// Applicable only for User Forms
|
|
emailOwnerOnSubmission: z.boolean(),
|
|
|
|
// Applicable only for Team Forms
|
|
sendUpdatesTo: z.array(z.number()).optional(),
|
|
sendToAll: z.boolean().optional(),
|
|
})
|
|
.nullable();
|
|
|
|
export const DeploymentTheme = z
|
|
.object({
|
|
brand: z.string().default("#292929"),
|
|
textBrand: z.string().default("#ffffff"),
|
|
darkBrand: z.string().default("#fafafa"),
|
|
textDarkBrand: z.string().default("#292929"),
|
|
bookingHighlight: z.string().default("#10B981"),
|
|
bookingLightest: z.string().default("#E1E1E1"),
|
|
bookingLighter: z.string().default("#ACACAC"),
|
|
bookingLight: z.string().default("#888888"),
|
|
bookingMedian: z.string().default("#494949"),
|
|
bookingDark: z.string().default("#313131"),
|
|
bookingDarker: z.string().default("#292929"),
|
|
fontName: z.string().default("Cal Sans"),
|
|
fontSrc: z.string().default("https://cal.com/cal.ttf"),
|
|
})
|
|
.optional();
|
|
|
|
export type ZodDenullish<T extends ZodTypeAny> = T extends ZodNullable<infer U> | ZodOptional<infer U>
|
|
? ZodDenullish<U>
|
|
: T;
|
|
|
|
export type ZodDenullishShape<T extends ZodRawShape> = {
|
|
[k in keyof T]: ZodDenullish<T[k]>;
|
|
};
|
|
|
|
export const denullish = <T extends ZodTypeAny>(schema: T): ZodDenullish<T> =>
|
|
(schema instanceof ZodNullable || schema instanceof ZodOptional
|
|
? denullish((schema._def as ZodNullableDef | ZodOptionalDef).innerType)
|
|
: schema) as ZodDenullish<T>;
|
|
|
|
type UnknownKeysParam = "passthrough" | "strict" | "strip";
|
|
|
|
/**
|
|
* @see https://github.com/3x071c/lsg-remix/blob/e2a9592ba3ec5103556f2cf307c32f08aeaee32d/app/lib/util/zod.ts
|
|
*/
|
|
export function denullishShape<
|
|
T extends ZodRawShape,
|
|
UnknownKeys extends UnknownKeysParam = "strip",
|
|
Catchall extends ZodTypeAny = ZodTypeAny,
|
|
Output = objectOutputType<T, Catchall>,
|
|
Input = objectInputType<T, Catchall>
|
|
>(
|
|
obj: ZodObject<T, UnknownKeys, Catchall, Output, Input>
|
|
): ZodObject<ZodDenullishShape<T>, UnknownKeys, Catchall> {
|
|
const a = entries(obj.shape).map(([field, schema]) => [field, denullish(schema)] as const) as {
|
|
[K in keyof T]: [K, ZodDenullish<T[K]>];
|
|
}[keyof T][];
|
|
return new ZodObject({
|
|
...obj._def,
|
|
shape: () => fromEntries(a) as unknown as ZodDenullishShape<T>, // TODO: Safely assert type
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Like Object.entries, but with actually useful typings
|
|
* @param obj The object to turn into a tuple array (`[key, value][]`)
|
|
* @returns The constructed tuple array from the given object
|
|
* @see https://github.com/3x071c/lsg-remix/blob/e2a9592ba3ec5103556f2cf307c32f08aeaee32d/app/lib/util/entries.ts
|
|
*/
|
|
export const entries = <O extends Record<string, unknown>>(
|
|
obj: O
|
|
): {
|
|
readonly [K in keyof O]: [K, O[K]];
|
|
}[keyof O][] => {
|
|
return Object.entries(obj) as {
|
|
[K in keyof O]: [K, O[K]];
|
|
}[keyof O][];
|
|
};
|
|
|
|
/**
|
|
* Returns a type with all readonly notations removed (traverses recursively on an object)
|
|
*/
|
|
type DeepWriteable<T> = T extends Readonly<{
|
|
-readonly [K in keyof T]: T[K];
|
|
}>
|
|
? {
|
|
-readonly [K in keyof T]: DeepWriteable<T[K]>;
|
|
}
|
|
: T; /* Make it work with readonly types (this is not strictly necessary) */
|
|
|
|
type FromEntries<T> = T extends [infer Keys, unknown][]
|
|
? { [K in Keys & PropertyKey]: Extract<T[number], [K, unknown]>[1] }
|
|
: never;
|
|
|
|
/**
|
|
* Like Object.fromEntries, but with actually useful typings
|
|
* @param arr The tuple array (`[key, value][]`) to turn into an object
|
|
* @returns Object constructed from the given entries
|
|
* @see https://github.com/3x071c/lsg-remix/blob/e2a9592ba3ec5103556f2cf307c32f08aeaee32d/app/lib/util/fromEntries.ts
|
|
*/
|
|
export const fromEntries = <
|
|
E extends [PropertyKey, unknown][] | ReadonlyArray<readonly [PropertyKey, unknown]>
|
|
>(
|
|
entries: E
|
|
): FromEntries<DeepWriteable<E>> => {
|
|
return Object.fromEntries(entries) as FromEntries<DeepWriteable<E>>;
|
|
};
|
|
|
|
export const getAccessLinkResponseSchema = z.object({
|
|
download_link: z.string().url(),
|
|
});
|
|
|
|
export type GetAccessLinkResponseSchema = z.infer<typeof getAccessLinkResponseSchema>;
|
|
|
|
/** Facilitates converting values from Select inputs to plain ones before submitting */
|
|
export const optionToValueSchema = <T extends z.ZodTypeAny>(valueSchema: T) =>
|
|
z
|
|
.object({
|
|
label: z.string(),
|
|
value: valueSchema,
|
|
})
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
.transform((foo) => (foo as any).value as z.infer<T>);
|
|
|
|
/**
|
|
* Allows parsing without losing original data inference.
|
|
* @url https://github.com/colinhacks/zod/discussions/1655#discussioncomment-4367368
|
|
*/
|
|
export const getParserWithGeneric =
|
|
<T extends AnyZodObject>(valueSchema: T) =>
|
|
<Data>(data: Data) => {
|
|
type Output = z.infer<T>;
|
|
type SimpleFormValues = string | number | null | undefined;
|
|
return valueSchema.parse(data) as {
|
|
// TODO: Invesitage why this broke on zod 3.22.2 upgrade
|
|
[key in keyof Data]: Data[key] extends SimpleFormValues ? Data[key] : Output[key];
|
|
};
|
|
};
|
|
export const sendDailyVideoRecordingEmailsSchema = z.object({
|
|
recordingId: z.string(),
|
|
bookingUID: z.string(),
|
|
});
|
|
|
|
export const downloadLinkSchema = z.object({
|
|
download_link: z.string(),
|
|
});
|
|
|
|
// All properties within event type that can and will be updated if needed
|
|
export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect, "id">]: true } = {
|
|
title: true,
|
|
description: true,
|
|
interfaceLanguage: true,
|
|
isInstantEvent: true,
|
|
instantMeetingParameters: true,
|
|
instantMeetingExpiryTimeOffsetInSeconds: true,
|
|
aiPhoneCallConfig: true,
|
|
currency: true,
|
|
periodDays: true,
|
|
position: true,
|
|
price: true,
|
|
slug: true,
|
|
length: true,
|
|
offsetStart: true,
|
|
locations: true,
|
|
hidden: true,
|
|
availability: true,
|
|
recurringEvent: true,
|
|
customInputs: true,
|
|
disableGuests: true,
|
|
disableCancelling: true,
|
|
disableRescheduling: true,
|
|
allowReschedulingCancelledBookings: true,
|
|
requiresConfirmation: true,
|
|
canSendCalVideoTranscriptionEmails: true,
|
|
requiresConfirmationForFreeEmail: true,
|
|
requiresConfirmationWillBlockSlot: true,
|
|
eventName: true,
|
|
metadata: true,
|
|
children: true,
|
|
hideCalendarNotes: true,
|
|
hideCalendarEventDetails: true,
|
|
minimumBookingNotice: true,
|
|
beforeEventBuffer: true,
|
|
afterEventBuffer: true,
|
|
successRedirectUrl: true,
|
|
seatsPerTimeSlot: true,
|
|
seatsShowAttendees: true,
|
|
seatsShowAvailabilityCount: true,
|
|
forwardParamsSuccessRedirect: true,
|
|
periodType: true,
|
|
hashedLink: true,
|
|
webhooks: true,
|
|
periodStartDate: true,
|
|
periodEndDate: true,
|
|
destinationCalendar: true,
|
|
periodCountCalendarDays: true,
|
|
bookingLimits: true,
|
|
onlyShowFirstAvailableSlot: true,
|
|
slotInterval: true,
|
|
scheduleId: true,
|
|
workflows: true,
|
|
bookingFields: true,
|
|
durationLimits: true,
|
|
maxActiveBookingsPerBooker: true,
|
|
maxActiveBookingPerBookerOfferReschedule: true,
|
|
lockTimeZoneToggleOnBookingPage: true,
|
|
lockedTimeZone: true,
|
|
requiresBookerEmailVerification: true,
|
|
assignAllTeamMembers: true,
|
|
isRRWeightsEnabled: true,
|
|
eventTypeColor: true,
|
|
allowReschedulingPastBookings: true,
|
|
hideOrganizerEmail: true,
|
|
rescheduleWithSameRoundRobinHost: true,
|
|
maxLeadThreshold: true,
|
|
customReplyToEmail: true,
|
|
bookingRequiresAuthentication: true,
|
|
};
|
|
|
|
// All properties that are defined as unlocked based on all managed props
|
|
// Eventually this is going to be just a default and the user can change the config through the UI
|
|
export const unlockedManagedEventTypeProps = {
|
|
locations: allManagedEventTypeProps.locations,
|
|
scheduleId: allManagedEventTypeProps.scheduleId,
|
|
destinationCalendar: allManagedEventTypeProps.destinationCalendar,
|
|
};
|
|
|
|
export const emailSchema = emailRegexSchema;
|
|
|
|
// The PR at https://github.com/colinhacks/zod/pull/2157 addresses this issue and improves email validation
|
|
// I introduced this refinement(to be used with z.email()) as a short term solution until we upgrade to a zod
|
|
// version that will include updates in the above PR.
|
|
export const emailSchemaRefinement = (value: string) => {
|
|
return emailRegex.test(value);
|
|
};
|
|
|
|
export const signupSchema = z.object({
|
|
// Username is marked optional here because it's requirement depends on if it's the Organization invite or a team invite which isn't easily done in zod
|
|
// It's better handled beyond zod in `validateAndGetCorrectedUsernameAndEmail`
|
|
username: z.string().optional(),
|
|
email: z.string().regex(emailRegex, { message: "Invalid email" }),
|
|
password: z.string().superRefine((data, ctx) => {
|
|
const isStrict = false;
|
|
const result = isPasswordValid(data, true, isStrict);
|
|
Object.keys(result).map((key: string) => {
|
|
if (!result[key as keyof typeof result]) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: [key],
|
|
message: key,
|
|
});
|
|
}
|
|
});
|
|
}),
|
|
language: z.string().optional(),
|
|
token: z.string().optional(),
|
|
});
|
|
|
|
export const ZVerifyCodeInputSchema = z.object({
|
|
email: emailSchema,
|
|
code: z.string(),
|
|
});
|
|
|
|
export type ZVerifyCodeInputSchema = z.infer<typeof ZVerifyCodeInputSchema>;
|
|
|
|
export const coerceToDate = z.coerce.date();
|
|
export const getStringAsNumberRequiredSchema = (t: TFunction) =>
|
|
z.string().min(1, t("error_required_field")).pipe(z.coerce.number());
|
|
|
|
export const bookingSeatDataSchema = z.object({
|
|
description: z.string().optional(),
|
|
responses: bookingResponses,
|
|
});
|
|
|
|
// Schema for decrypted service account key
|
|
export const serviceAccountKeySchema = z
|
|
.object({
|
|
private_key: z.string(),
|
|
client_email: z.string().optional(),
|
|
client_id: z.string(),
|
|
tenant_id: z.string().optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
export type TServiceAccountKeySchema = z.infer<typeof serviceAccountKeySchema>;
|
|
|
|
export const rrSegmentQueryValueSchema = zodAttributesQueryValue.nullish();
|