diff --git a/apps/web/test/lib/handleChildrenEventTypes.test.ts b/apps/web/test/lib/handleChildrenEventTypes.test.ts index eec979b593..f53459e734 100644 --- a/apps/web/test/lib/handleChildrenEventTypes.test.ts +++ b/apps/web/test/lib/handleChildrenEventTypes.test.ts @@ -143,9 +143,10 @@ describe("handleChildrenEventTypes", () => { profileId: null, updatedValues: {}, }); + const { createdAt, updatedAt, ...expectedEvType } = evType; expect(prismaMock.eventType.create).toHaveBeenCalledWith({ data: { - ...evType, + ...expectedEvType, parentId: 1, users: { connect: [{ id: 4 }] }, lockTimeZoneToggleOnBookingPage: false, @@ -206,7 +207,7 @@ describe("handleChildrenEventTypes", () => { bookingLimits: undefined, }, }); - const { profileId, autoTranslateDescriptionEnabled, ...rest } = evType; + const { profileId, autoTranslateDescriptionEnabled, createdAt, updatedAt, ...rest } = evType; expect(prismaMock.eventType.update).toHaveBeenCalledWith({ data: { ...rest, @@ -317,9 +318,10 @@ describe("handleChildrenEventTypes", () => { profileId: null, updatedValues: {}, }); + const { createdAt, updatedAt, ...expectedEvType } = evType; expect(prismaMock.eventType.create).toHaveBeenCalledWith({ data: { - ...evType, + ...expectedEvType, parentId: 1, users: { connect: [{ id: 4 }] }, bookingLimits: undefined, @@ -383,7 +385,7 @@ describe("handleChildrenEventTypes", () => { length: 30, }, }); - const { profileId, autoTranslateDescriptionEnabled, ...rest } = evType; + const { profileId, autoTranslateDescriptionEnabled, createdAt, updatedAt, ...rest } = evType; expect(prismaMock.eventType.update).toHaveBeenCalledWith({ data: { ...rest, @@ -464,7 +466,11 @@ describe("handleChildrenEventTypes", () => { ...evType, }; - prismaMock.eventType.update.mockResolvedValue(mockUpdatedEventType); + prismaMock.eventType.update.mockResolvedValue({ + ...mockUpdatedEventType, + createdAt: new Date(), + updatedAt: new Date(), + }); await updateChildrenEventTypes({ eventTypeId: 1, @@ -480,9 +486,10 @@ describe("handleChildrenEventTypes", () => { updatedValues: {}, }); + const { createdAt, updatedAt, ...expectedEvType } = evType; expect(prismaMock.eventType.create).toHaveBeenCalledWith({ data: { - ...evType, + ...expectedEvType, bookingLimits: undefined, durationLimits: undefined, recurringEvent: undefined, @@ -507,7 +514,7 @@ describe("handleChildrenEventTypes", () => { allowReschedulingCancelledBookings: false, }, }); - const { profileId, rrSegmentQueryValue, ...rest } = evType; + const { profileId, rrSegmentQueryValue, createdAt: _, updatedAt: __, ...rest } = evType; if ("workflows" in rest) delete rest.workflows; expect(prismaMock.eventType.update).toHaveBeenCalledWith({ data: { diff --git a/packages/features/eventtypes/lib/defaultEvents.ts b/packages/features/eventtypes/lib/defaultEvents.ts index 40d4dd8243..b50efc84f1 100644 --- a/packages/features/eventtypes/lib/defaultEvents.ts +++ b/packages/features/eventtypes/lib/defaultEvents.ts @@ -149,6 +149,8 @@ const commons = { eventTypeColor: null, hostGroups: [], bookingRequiresAuthentication: false, + createdAt: null, + updatedAt: null, }; export const dynamicEvent = { diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts index 4db4ff722c..119dff6bdb 100644 --- a/packages/lib/test/builder.ts +++ b/packages/lib/test/builder.ts @@ -162,6 +162,8 @@ export const buildEventType = (eventType?: Partial): EventType => { restrictionScheduleId: null, useBookerTimezone: false, bookingRequiresAuthentication: false, + createdAt: null, + updatedAt: null, ...eventType, }; }; diff --git a/packages/prisma/extensions/event-type-timestamps.ts b/packages/prisma/extensions/event-type-timestamps.ts new file mode 100644 index 0000000000..9f1f8d5388 --- /dev/null +++ b/packages/prisma/extensions/event-type-timestamps.ts @@ -0,0 +1,27 @@ +import { Prisma } from "@calcom/prisma/client"; + +export function eventTypeTimestampsExtension() { + return Prisma.defineExtension({ + query: { + eventType: { + async create({ args, query }) { + const now = new Date(); + args.data.createdAt = now; + return query(args); + }, + async createMany({ args, query }) { + const now = new Date(); + if (Array.isArray(args.data)) { + args.data = args.data.map((item) => ({ + ...item, + createdAt: now, + })); + } else { + args.data.createdAt = now; + } + return query(args); + }, + }, + }, + }); +} diff --git a/packages/prisma/index.ts b/packages/prisma/index.ts index c014de6c7d..03a4456ab4 100644 --- a/packages/prisma/index.ts +++ b/packages/prisma/index.ts @@ -2,6 +2,7 @@ import { PrismaClient, type Prisma } from "@calcom/prisma/client"; import { bookingIdempotencyKeyExtension } from "./extensions/booking-idempotency-key"; import { disallowUndefinedDeleteUpdateManyExtension } from "./extensions/disallow-undefined-delete-update-many"; +import { eventTypeTimestampsExtension } from "./extensions/event-type-timestamps"; import { excludeLockedUsersExtension } from "./extensions/exclude-locked-users"; import { excludePendingPaymentsExtension } from "./extensions/exclude-pending-payment-teams"; import { usageTrackingExtention } from "./extensions/usage-tracking"; @@ -42,6 +43,7 @@ export const customPrisma = (options?: Prisma.PrismaClientOptions) => .$extends(excludeLockedUsersExtension()) .$extends(excludePendingPaymentsExtension()) .$extends(bookingIdempotencyKeyExtension()) + .$extends(eventTypeTimestampsExtension()) .$extends(disallowUndefinedDeleteUpdateManyExtension()) as unknown as PrismaClient; // If any changed on middleware server restart is required @@ -58,6 +60,7 @@ export const prisma: PrismaClient = baseClient .$extends(excludeLockedUsersExtension()) .$extends(excludePendingPaymentsExtension()) .$extends(bookingIdempotencyKeyExtension()) + .$extends(eventTypeTimestampsExtension()) .$extends(disallowUndefinedDeleteUpdateManyExtension()) as unknown as PrismaClient; // This prisma instance is meant to be used only for READ operations. diff --git a/packages/prisma/migrations/20250919174231_add_event_type_timestamps/migration.sql b/packages/prisma/migrations/20250919174231_add_event_type_timestamps/migration.sql new file mode 100644 index 0000000000..46ad7995c0 --- /dev/null +++ b/packages/prisma/migrations/20250919174231_add_event_type_timestamps/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "EventType" ADD COLUMN "createdAt" TIMESTAMP(3), +ADD COLUMN "updatedAt" TIMESTAMP(3); diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 71a60d98d6..2bf272ff2c 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -244,6 +244,9 @@ model EventType { bookingRequiresAuthentication Boolean @default(false) + createdAt DateTime? + updatedAt DateTime? @updatedAt + @@unique([userId, slug]) @@unique([teamId, slug]) @@unique([userId, parentId])