feat: add createdAt and updatedAt fields to EventType model (#23949)

* feat: add createdAt and updatedAt fields to EventType model

- Add timestamp fields to EventType schema with backward compatibility
- Update all EventType select statements across API v1, v2, and tRPC
- Update platform API output types to include timestamp fields
- Generate migration for existing database records
- Fix test files to handle new timestamp fields properly

The timestamp fields are added as optional (DateTime?) to ensure backward
compatibility - existing EventType records will have null values for these
fields, while new records will automatically get timestamps.

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* fix: remove @default(now()) from EventType createdAt to prevent existing records from getting current timestamp

- Remove @default(now()) from createdAt field in schema to ensure backward compatibility
- Create migration that only drops DEFAULT constraint without updating existing records
- Existing EventType records will keep null timestamps
- New records will get timestamps via application logic

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* feat: add createdAt and updatedAt timestamps to all EventType creation locations

- Update all seed scripts to set timestamps for new EventType records
- Ensure consistency across all EventType creation patterns
- Maintain backward compatibility with nullable timestamp fields

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* feat: add timestamps to remaining EventType creation locations in test files

- Update integration test files to include createdAt and updatedAt
- Ensure consistency across all EventType creation patterns
- Complete comprehensive update of all EventType creation locations

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* add migration

* few left

* feat: implement Prisma extension for automatic EventType timestamps

- Create eventTypeTimestampsExtension to automatically set createdAt and updatedAt
- Handle both eventType.create and eventType.createMany operations
- Revert all manual timestamp setting from application code
- Extension only sets timestamps if not already provided
- Maintains backward compatibility - existing records keep null timestamps
- New records get automatic timestamps via Prisma extension
- Follows existing Cal.com extension patterns and architecture

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* feat: remove hardcoded timestamp settings from test files

- Remove manual createdAt/updatedAt settings from routing forms controller test
- Remove manual timestamp settings from event types repository fixture
- Prisma extension now handles timestamps automatically for all EventType operations

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* update

* Update event-type.output.ts

* fix test

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2025-09-24 06:59:46 +01:00
1 parent 608fe1c586
commit c2472cc05a
7 files changed
+54 -7

No files matched your search

@@ -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: {
@@ -149,6 +149,8 @@ const commons = {
eventTypeColor: null,
hostGroups: [],
bookingRequiresAuthentication: false,
createdAt: null,
updatedAt: null,
};
export const dynamicEvent = {
+2
View File
@@ -162,6 +162,8 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
restrictionScheduleId: null,
useBookerTimezone: false,
bookingRequiresAuthentication: false,
createdAt: null,
updatedAt: null,
...eventType,
};
};
@@ -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);
},
},
},
});
}
+3
View File
@@ -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.
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "createdAt" TIMESTAMP(3),
ADD COLUMN "updatedAt" TIMESTAMP(3);
+3
View File
@@ -244,6 +244,9 @@ model EventType {
bookingRequiresAuthentication Boolean @default(false)
createdAt DateTime?
updatedAt DateTime? @updatedAt
@@unique([userId, slug])
@@unique([teamId, slug])
@@unique([userId, parentId])