diff --git a/artifacts/api-server/src/routes/executive-meetings.ts b/artifacts/api-server/src/routes/executive-meetings.ts index 7b246d0d..4952eeca 100644 --- a/artifacts/api-server/src/routes/executive-meetings.ts +++ b/artifacts/api-server/src/routes/executive-meetings.ts @@ -228,66 +228,96 @@ const duplicateSchema = z.object({ targetDate: dateSchema, }); -const isoDateOrDatetime = z.union([ - z.string().regex(/^\d{4}-\d{2}-\d{2}$/), - z.string().datetime(), -]); -const trimmedString = (max: number) => z.string().trim().min(1).max(max); +const dateOnly = z.string().regex(/^\d{4}-\d{2}-\d{2}$/); +const timeHm = z.string().regex(/^\d{2}:\d{2}(:\d{2})?$/); const detailsByType = { - create: z.object({ - titleAr: trimmedString(200).optional(), - titleEn: trimmedString(200).optional(), - meetingDate: isoDateOrDatetime.optional(), - notes: z.string().trim().max(2000).optional(), - }), - edit: z.object({ - titleAr: z.string().trim().max(200).optional(), - titleEn: z.string().trim().max(200).optional(), - notes: z.string().trim().max(2000).optional(), - }), - delete: z.object({ - reason: z.string().trim().max(2000).optional(), - }), - reschedule: z.object({ - newDate: isoDateOrDatetime, - newTime: z.string().regex(/^\d{2}:\d{2}(:\d{2})?$/).optional(), - notes: z.string().trim().max(2000).optional(), - }), - add_attendee: z.object({ - nameAr: trimmedString(200), - nameEn: z.string().trim().max(200).optional(), - role: z.string().trim().max(120).optional(), - notes: z.string().trim().max(2000).optional(), - }), - remove_attendee: z.object({ - attendeeId: z.number().int().positive().optional(), - nameAr: z.string().trim().max(200).optional(), - notes: z.string().trim().max(2000).optional(), - }), - change_location: z.object({ - location: trimmedString(200), - notes: z.string().trim().max(2000).optional(), - }), - cancel_meeting: z.object({ - reason: z.string().trim().max(2000).optional(), - }), - note: z + create: z .object({ - text: z.string().trim().max(2000).optional(), - note: z.string().trim().max(2000).optional(), + titleAr: z.string().trim().max(300).optional(), + titleEn: z.string().trim().max(300).optional(), + meetingDate: dateOnly.optional(), + startTime: timeHm.optional(), + endTime: timeHm.optional(), + location: z.string().trim().max(300).optional(), + notes: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()), + edit: z + .object({ + titleAr: z.string().trim().max(300).optional(), + titleEn: z.string().trim().max(300).optional(), + notes: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()), + delete: z + .object({ + reason: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()), + reschedule: z + .object({ + meetingDate: dateOnly.optional(), + startTime: timeHm.optional(), + endTime: timeHm.optional(), + notes: z.string().trim().max(5000).optional(), }) .catchall(z.unknown()) .refine( - (v) => Boolean((v.text ?? v.note ?? "").trim()), + (v) => Boolean(v.meetingDate || v.startTime || v.endTime), + { message: "reschedule requires meetingDate, startTime, or endTime" }, + ), + add_attendee: z + .object({ + name: z.string().trim().min(1).max(200), + title: z.string().trim().max(200).optional(), + attendanceType: z.enum(ATTENDANCE_TYPES).optional(), + notes: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()), + remove_attendee: z + .object({ + attendeeId: z.number().int().positive(), + notes: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()), + change_location: z + .object({ + location: z.string().trim().max(300).optional(), + meetingUrl: z.string().trim().max(1000).optional(), + platform: z.enum(PLATFORMS).optional(), + notes: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()) + .refine( + (v) => Boolean(v.location || v.meetingUrl || v.platform), + { message: "change_location requires location, meetingUrl, or platform" }, + ), + cancel_meeting: z + .object({ + reason: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()), + note: z + .object({ + note: z.string().trim().max(5000).optional(), + text: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()) + .refine( + (v) => Boolean((v.note ?? v.text ?? "").trim()), { message: "note text required" }, ), - highlight: z.object({ - notes: z.string().trim().max(2000).optional(), - }), - unhighlight: z.object({ - notes: z.string().trim().max(2000).optional(), - }), + highlight: z + .object({ + notes: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()), + unhighlight: z + .object({ + notes: z.string().trim().max(5000).optional(), + }) + .catchall(z.unknown()), other: z.object({}).catchall(z.unknown()), } as const;