diff --git a/packages/schemas/src/events.ts b/packages/schemas/src/events.ts index 24793867..90ea5e99 100644 --- a/packages/schemas/src/events.ts +++ b/packages/schemas/src/events.ts @@ -6,6 +6,8 @@ import { } from "temporal-zod"; import * as z from "zod"; +import { isBefore, isSameType } from "@repo/temporal"; + const conferenceEntryPointSchema = z.object({ joinUrl: z.object({ label: z.string().optional(), @@ -205,33 +207,66 @@ export const recurrenceSchema = z }, ); -export const createEventInputSchema = z.object({ - id: z.string(), - title: z.string().optional(), - start: dateInputSchema, - end: dateInputSchema, - allDay: z.boolean().optional(), - recurrence: recurrenceSchema.optional(), - recurringEventId: z.string().optional(), - description: z.string().optional(), - location: z.string().optional(), - availability: z.enum(["busy", "free"]).optional(), - color: z.string().optional(), - visibility: z - .enum(["default", "public", "private", "confidential"]) - .optional(), - accountId: z.string(), - calendarId: z.string(), - providerId: z.enum(["google", "microsoft"]), - readOnly: z.boolean(), - metadata: z.union([microsoftMetadataSchema, googleMetadataSchema]).optional(), - attendees: z.array(attendeeSchema).optional(), - conference: conferenceSchema.optional(), - createdAt: z.instanceof(Temporal.Instant).optional(), - updatedAt: z.instanceof(Temporal.Instant).optional(), -}); +export const createEventInputSchema = z + .object({ + id: z.string(), + title: z.string().optional(), + start: dateInputSchema, + end: dateInputSchema, + allDay: z.boolean().optional(), + recurrence: recurrenceSchema.optional(), + recurringEventId: z.string().optional(), + description: z.string().optional(), + location: z.string().optional(), + availability: z.enum(["busy", "free"]).optional(), + color: z.string().optional(), + visibility: z + .enum(["default", "public", "private", "confidential"]) + .optional(), + accountId: z.string(), + calendarId: z.string(), + providerId: z.enum(["google", "microsoft"]), + readOnly: z.boolean(), + metadata: z + .union([microsoftMetadataSchema, googleMetadataSchema]) + .optional(), + attendees: z.array(attendeeSchema).optional(), + conference: conferenceSchema.optional(), + createdAt: z.instanceof(Temporal.Instant).optional(), + updatedAt: z.instanceof(Temporal.Instant).optional(), + }) + .refine( + (data) => { + if (!isSameType(data.start, data.end)) { + return false; + } + if ( + data.start instanceof Temporal.PlainDate && + data.end instanceof Temporal.PlainDate + ) { + return isBefore(data.start, data.end); + } + if ( + data.start instanceof Temporal.Instant && + data.end instanceof Temporal.Instant + ) { + return isBefore(data.start, data.end); + } + if ( + data.start instanceof Temporal.ZonedDateTime && + data.end instanceof Temporal.ZonedDateTime + ) { + return isBefore(data.start, data.end); + } + return false; + }, + { + message: "Event start time must be earlier than end time", + path: ["start"], + }, + ); -export const updateEventInputSchema = createEventInputSchema.extend({ +export const updateEventInputSchema = createEventInputSchema.safeExtend({ id: z.string(), etag: z.string().optional(), metadata: z.union([microsoftMetadataSchema, googleMetadataSchema]).optional(), diff --git a/packages/temporal/src/compare.ts b/packages/temporal/src/compare.ts index c09126f9..f36b9fd7 100644 --- a/packages/temporal/src/compare.ts +++ b/packages/temporal/src/compare.ts @@ -286,6 +286,13 @@ export function isToday( return today.equals(toPlainDate(value, { timeZone })); } +export function isSameType( + a: TemporalConvertible, + b: TemporalConvertible, +): boolean { + return a.constructor === b.constructor; +} + export function isBefore(a: Temporal.PlainDate, b: Temporal.PlainDate): boolean; export function isBefore( a: Temporal.ZonedDateTime,