79a09362c9
* refactor: remove circular dependency between prisma and app-store packages - Replace EventTypeAppMetadataSchema with z.record(z.any()).optional() pattern - Remove appDataSchemas import from packages/prisma/zod-utils.ts - Add null checks in consuming packages for flexible validation - Fix test file that no longer needs @ts-expect-error directive This breaks the circular dependency while maintaining all functionality by moving strict validation to the business logic layer where operations actually happen, following existing patterns in the codebase. Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * refactor: remove EventTypeAppMetadataSchema exports from prisma package - Remove EventTypeAppMetadataSchema and eventTypeAppMetadataOptionalSchema exports from prisma/zod-utils.ts - Update all importing files to use local z.record(z.any()).optional() schemas - Replace type annotations with Record<string, any> where appropriate - Maintain validation functionality while breaking circular dependency - All TypeScript compilation now passes without errors Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * refactor: complete removal of EventTypeAppMetadataSchema from remaining files - Update handleSeats/createNewSeat.ts to use local schema - Update payment handlers to use local schemas - Update eventTypes update handler to use local schema - All files now define their own validation instead of importing from prisma - Circular dependency completely eliminated Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * fix: resolve TypeScript compilation errors - Remove duplicate z import from handleConfirmation.ts - Remove duplicate appDataSchemas import from update.handler.ts - Fix index signature errors in handlePayment.ts by using appData variable consistently - All type checks now pass successfully Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * fix: add type casting for appSlug in eventTypeService - Cast appSlug as keyof typeof apps to resolve index signature error - Maintains type safety while allowing dynamic property access Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * fix * remove * make zod-utils.ts in app-store * update imports * fix * fix * fix * revert unrelated change * update imports * fix * fix * revert * fix * fix --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: hbjORbj <sldisek783@gmail.com>
216 lines
6.3 KiB
TypeScript
216 lines
6.3 KiB
TypeScript
import { DailyLocationType } from "@calcom/app-store/constants";
|
|
import slugify from "@calcom/lib/slugify";
|
|
import type { Prisma, SelectedCalendar } from "@calcom/prisma/client";
|
|
import { PeriodType, SchedulingType } from "@calcom/prisma/enums";
|
|
import type { userSelect } from "@calcom/prisma/selects";
|
|
import type { CustomInputSchema } from "@calcom/prisma/zod-utils";
|
|
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
|
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|
import type { CredentialPayload } from "@calcom/types/Credential";
|
|
|
|
type User = Omit<Prisma.UserGetPayload<{ select: typeof userSelect }>, "selectedCalendars"> & {
|
|
allSelectedCalendars: SelectedCalendar[];
|
|
userLevelSelectedCalendars: SelectedCalendar[];
|
|
};
|
|
|
|
type UsernameSlugLinkProps = {
|
|
users: {
|
|
id?: number;
|
|
username: string | null;
|
|
email?: string;
|
|
name?: string | null;
|
|
bio?: string | null;
|
|
avatar?: string | null;
|
|
theme?: string | null;
|
|
away?: boolean;
|
|
verified?: boolean | null;
|
|
allowDynamicBooking?: boolean | null;
|
|
}[];
|
|
slug: string;
|
|
};
|
|
|
|
const user: User & { credentials: CredentialPayload[] } = {
|
|
metadata: null,
|
|
theme: null,
|
|
credentials: [],
|
|
username: "john.doe",
|
|
timeZone: "",
|
|
bufferTime: 0,
|
|
availability: [],
|
|
id: 0,
|
|
startTime: 0,
|
|
endTime: 0,
|
|
allSelectedCalendars: [],
|
|
userLevelSelectedCalendars: [],
|
|
schedules: [],
|
|
defaultScheduleId: null,
|
|
locale: "en",
|
|
email: "john.doe@example.com",
|
|
name: "John doe",
|
|
destinationCalendar: null,
|
|
hideBranding: true,
|
|
brandColor: "#797979",
|
|
darkBrandColor: "#efefef",
|
|
allowDynamicBooking: true,
|
|
timeFormat: 12,
|
|
travelSchedules: [],
|
|
locked: false,
|
|
isPlatformManaged: false,
|
|
};
|
|
|
|
const customInputs: CustomInputSchema[] = [];
|
|
|
|
const commons = {
|
|
isDynamic: true,
|
|
periodCountCalendarDays: true,
|
|
periodStartDate: null,
|
|
periodEndDate: null,
|
|
beforeEventBuffer: 0,
|
|
afterEventBuffer: 0,
|
|
periodType: PeriodType.UNLIMITED,
|
|
periodDays: null,
|
|
slotInterval: null,
|
|
offsetStart: 0,
|
|
locations: [{ type: DailyLocationType }],
|
|
customInputs,
|
|
disableGuests: true,
|
|
minimumBookingNotice: 120,
|
|
schedule: null,
|
|
timeZone: null,
|
|
successRedirectUrl: "",
|
|
forwardParamsSuccessRedirect: true,
|
|
teamId: null,
|
|
scheduleId: null,
|
|
availability: [],
|
|
price: 0,
|
|
currency: "usd",
|
|
schedulingType: SchedulingType.COLLECTIVE,
|
|
seatsPerTimeSlot: null,
|
|
seatsShowAttendees: null,
|
|
seatsShowAvailabilityCount: null,
|
|
disableCancelling: false,
|
|
disableRescheduling: false,
|
|
onlyShowFirstAvailableSlot: false,
|
|
allowReschedulingPastBookings: false,
|
|
hideOrganizerEmail: false,
|
|
showOptimizedSlots: false,
|
|
id: 0,
|
|
hideCalendarNotes: false,
|
|
hideCalendarEventDetails: false,
|
|
recurringEvent: null,
|
|
destinationCalendar: null,
|
|
team: null,
|
|
lockTimeZoneToggleOnBookingPage: false,
|
|
lockedTimeZone: null,
|
|
requiresConfirmation: false,
|
|
requiresConfirmationForFreeEmail: false,
|
|
requiresBookerEmailVerification: false,
|
|
bookingLimits: null,
|
|
maxActiveBookingsPerBooker: null,
|
|
maxActiveBookingPerBookerOfferReschedule: false,
|
|
durationLimits: null,
|
|
hidden: false,
|
|
userId: 0,
|
|
parentId: null,
|
|
parent: null,
|
|
owner: null,
|
|
workflows: [],
|
|
users: [user],
|
|
hosts: [],
|
|
subsetOfHosts: [],
|
|
metadata: EventTypeMetaDataSchema.parse({}),
|
|
bookingFields: [],
|
|
assignAllTeamMembers: false,
|
|
assignRRMembersUsingSegment: false,
|
|
rrSegmentQueryValue: null,
|
|
isRRWeightsEnabled: false,
|
|
rescheduleWithSameRoundRobinHost: false,
|
|
useEventTypeDestinationCalendarEmail: false,
|
|
secondaryEmailId: null,
|
|
secondaryEmail: null,
|
|
autoTranslateDescriptionEnabled: false,
|
|
fieldTranslations: [],
|
|
maxLeadThreshold: null,
|
|
includeNoShowInRRCalculation: false,
|
|
useEventLevelSelectedCalendars: false,
|
|
rrResetInterval: null,
|
|
rrTimestampBasis: null,
|
|
interfaceLanguage: null,
|
|
customReplyToEmail: null,
|
|
restrictionScheduleId: null,
|
|
useBookerTimezone: false,
|
|
profileId: null,
|
|
requiresConfirmationWillBlockSlot: false,
|
|
canSendCalVideoTranscriptionEmails: false,
|
|
instantMeetingExpiryTimeOffsetInSeconds: 0,
|
|
instantMeetingScheduleId: null,
|
|
instantMeetingParameters: [],
|
|
eventTypeColor: null,
|
|
hostGroups: [],
|
|
bookingRequiresAuthentication: false,
|
|
};
|
|
|
|
export const dynamicEvent = {
|
|
length: 30,
|
|
slug: "dynamic",
|
|
title: "Group Meeting",
|
|
eventName: "Group Meeting",
|
|
description: "Join us for a meeting with multiple people",
|
|
descriptionAsSafeHTML: "",
|
|
position: 0,
|
|
...commons,
|
|
metadata: eventTypeMetaDataSchemaWithTypedApps.parse({ multipleDuration: [15, 30, 45, 60, 90] }),
|
|
};
|
|
|
|
export const defaultEvents = [dynamicEvent];
|
|
|
|
export const getDynamicEventDescription = (dynamicUsernames: string[], slug: string): string => {
|
|
return `Book a ${slug} min event with ${dynamicUsernames.join(", ")}`;
|
|
};
|
|
|
|
export const getDynamicEventName = (dynamicNames: string[], slug: string): string => {
|
|
const lastUser = dynamicNames.pop();
|
|
return `Dynamic Collective ${slug} min event with ${dynamicNames.join(", ")} & ${lastUser}`;
|
|
};
|
|
|
|
export const getDefaultEvent = (slug: string) => {
|
|
const event = defaultEvents.find((obj) => {
|
|
return obj.slug === slug;
|
|
});
|
|
return event || dynamicEvent;
|
|
};
|
|
|
|
export const getGroupName = (usernameList: string[]): string => {
|
|
return usernameList.join(", ");
|
|
};
|
|
|
|
export const getUsernameSlugLink = ({ users, slug }: UsernameSlugLinkProps): string => {
|
|
let slugLink = ``;
|
|
if (users.length > 1) {
|
|
const combinedUsername = users.map((user) => user.username).join("+");
|
|
slugLink = `/${combinedUsername}/${slug}`;
|
|
} else {
|
|
slugLink = `/${users[0].username}/${slug}`;
|
|
}
|
|
return slugLink;
|
|
};
|
|
|
|
const arrayCast = (value: unknown | unknown[]) => {
|
|
return Array.isArray(value) ? value : value ? [value] : [];
|
|
};
|
|
|
|
export const getUsernameList = (users: string | string[] | undefined): string[] => {
|
|
// Multiple users can come in case of a team round-robin booking and in that case dynamic link won't be a user.
|
|
// So, even though this code handles even if individual user is dynamic link, that isn't a possibility right now.
|
|
users = arrayCast(users);
|
|
const allUsers = users
|
|
.map((user) => user.replace(/( |%20|%2b)/gi, "+").split("+"))
|
|
.flat()
|
|
.filter(Boolean);
|
|
return Array.prototype.concat(...allUsers.map((userSlug) => slugify(userSlug)));
|
|
};
|
|
|
|
export default defaultEvents;
|
|
|
|
export type DefaultEvent = Awaited<ReturnType<typeof getDefaultEvent>>;
|