diff --git a/app/features/calendar/routes/calendar.new.tsx b/app/features/calendar/routes/calendar.new.tsx index 0b7403e37..124593b37 100644 --- a/app/features/calendar/routes/calendar.new.tsx +++ b/app/features/calendar/routes/calendar.new.tsx @@ -93,58 +93,75 @@ export const meta: MetaFunction = (args) => { return [{ title: data.title }]; }; -const newCalendarEventActionSchema = z.object({ - eventToEditId: z.preprocess(actualNumber, id.nullish()), - name: z - .string() - .min(CALENDAR_EVENT.NAME_MIN_LENGTH) - .max(CALENDAR_EVENT.NAME_MAX_LENGTH), - description: z.preprocess( - falsyToNull, - z.string().max(CALENDAR_EVENT.DESCRIPTION_MAX_LENGTH).nullable(), - ), - date: z.preprocess( - toArray, - z - .array(z.preprocess(date, z.date().min(MIN_DATE).max(MAX_DATE))) - .min(1) - .max(CALENDAR_EVENT.MAX_AMOUNT_OF_DATES), - ), - bracketUrl: z - .string() - .url() - .max(CALENDAR_EVENT.BRACKET_URL_MAX_LENGTH) - .default("https://sendou.ink"), - discordInviteCode: z.preprocess( - falsyToNull, - z.string().max(CALENDAR_EVENT.DISCORD_INVITE_CODE_MAX_LENGTH).nullable(), - ), - tags: z.preprocess( - processMany(safeJSONParse, removeDuplicates), - z - .array( - z - .string() - .refine((val) => - CALENDAR_EVENT.TAGS.includes(val as CalendarEventTag), - ), - ) - .nullable(), - ), - badges: z.preprocess( - processMany(safeJSONParse, removeDuplicates), - z.array(id).nullable(), - ), - pool: z.string().optional(), - toToolsEnabled: z.preprocess(checkboxValueToBoolean, z.boolean()), - toToolsMode: z.enum(["ALL", "SZ", "TC", "RM", "CB"]).optional(), -}); +const newCalendarEventActionSchema = z + .object({ + eventToEditId: z.preprocess(actualNumber, id.nullish()), + name: z + .string() + .min(CALENDAR_EVENT.NAME_MIN_LENGTH) + .max(CALENDAR_EVENT.NAME_MAX_LENGTH), + description: z.preprocess( + falsyToNull, + z.string().max(CALENDAR_EVENT.DESCRIPTION_MAX_LENGTH).nullable(), + ), + date: z.preprocess( + toArray, + z + .array(z.preprocess(date, z.date().min(MIN_DATE).max(MAX_DATE))) + .min(1) + .max(CALENDAR_EVENT.MAX_AMOUNT_OF_DATES), + ), + bracketUrl: z + .string() + .url() + .max(CALENDAR_EVENT.BRACKET_URL_MAX_LENGTH) + .default("https://sendou.ink"), + discordInviteCode: z.preprocess( + falsyToNull, + z.string().max(CALENDAR_EVENT.DISCORD_INVITE_CODE_MAX_LENGTH).nullable(), + ), + tags: z.preprocess( + processMany(safeJSONParse, removeDuplicates), + z + .array( + z + .string() + .refine((val) => + CALENDAR_EVENT.TAGS.includes(val as CalendarEventTag), + ), + ) + .nullable(), + ), + badges: z.preprocess( + processMany(safeJSONParse, removeDuplicates), + z.array(id).nullable(), + ), + pool: z.string().optional(), + toToolsEnabled: z.preprocess(checkboxValueToBoolean, z.boolean()), + toToolsMode: z.enum(["ALL", "SZ", "TC", "RM", "CB"]).optional(), + }) + .refine( + async (schema) => { + if (schema.eventToEditId) { + const eventToEdit = await CalendarRepository.findById({ + id: schema.eventToEditId, + }); + return schema.date.length === 1 || !eventToEdit?.tournamentId; + } else { + return schema.date.length === 1 || !schema.toToolsEnabled; + } + }, + { + message: "Tournament must have exactly one date", + }, + ); export const action: ActionFunction = async ({ request }) => { const user = await requireUser(request); const data = await parseRequestFormData({ request, schema: newCalendarEventActionSchema, + parseAsync: true, }); validate(canAddNewEvent(user), "Not authorized", 401); diff --git a/app/features/tournament/TournamentRepository.server.ts b/app/features/tournament/TournamentRepository.server.ts index f112293dc..7aab477d7 100644 --- a/app/features/tournament/TournamentRepository.server.ts +++ b/app/features/tournament/TournamentRepository.server.ts @@ -10,7 +10,6 @@ export async function findById(id: number) { const row = await db .selectFrom("Tournament") .innerJoin("CalendarEvent", "Tournament.id", "CalendarEvent.tournamentId") - // TODO: it does not support multiple dates .innerJoin( "CalendarEventDate", "CalendarEvent.id", diff --git a/app/utils/remix.ts b/app/utils/remix.ts index 6b2f2b52e..1ff71dd4e 100644 --- a/app/utils/remix.ts +++ b/app/utils/remix.ts @@ -47,12 +47,18 @@ export function parseSearchParams({ export async function parseRequestFormData({ request, schema, + parseAsync, }: { request: Request; schema: T; + parseAsync?: boolean; }): Promise> { try { - return schema.parse(formDataToObject(await request.formData())); + const parsed = parseAsync + ? await schema.parseAsync(formDataToObject(await request.formData())) + : schema.parse(formDataToObject(await request.formData())); + + return parsed; } catch (e) { if (e instanceof z.ZodError) { console.error(e);