diff --git a/app/core/stages/stages.ts b/app/core/stages/stages.ts index 1e87bf32d..7a2101408 100644 --- a/app/core/stages/stages.ts +++ b/app/core/stages/stages.ts @@ -29,13 +29,7 @@ export const stages = [ "Skipper Pavilion", ] as const; -export const modesShort: readonly Mode[] = [ - "TW", - "SZ", - "TC", - "RM", - "CB", -] as const; +export const modesShort: Mode[] = ["TW", "SZ", "TC", "RM", "CB"]; export const modesShortToLong: Record = { TW: "Turf War", SZ: "Splat Zones", diff --git a/app/routes/match-details.ts b/app/routes/match-details.ts new file mode 100644 index 000000000..4b5bbdf38 --- /dev/null +++ b/app/routes/match-details.ts @@ -0,0 +1,100 @@ +import type { ActionFunction } from "remix"; +import { z } from "zod"; +import { weapons } from "~/constants"; +import { modesShort, stages } from "~/core/stages/stages"; +import { parseRequestFormData } from "~/utils"; + +export const abilityEnum = z.enum([ + "ISM", + "ISS", + "REC", + "RSU", + "SSU", + "SCU", + "SS", + "SPU", + "QR", + "QSJ", + "BRU", + "RES", + "BDU", + "MPU", + "OG", + "LDE", + "T", + "CB", + "NS", + "H", + "TI", + "RP", + "AD", + "SJ", + "OS", + "DR", +]); + +const playerSchema = z.object({ + principal_id: z.string(), + name: z.string().min(1).max(10), + weapon: z.enum(weapons), + main_abilities: z.array(abilityEnum), + sub_abilities: z.array(z.array(abilityEnum)), + kills: z.number().int().min(0).max(50), + assists: z.number().int().min(0).max(50), + deaths: z.number().int().min(0).max(50), + specials: z.number().int().min(0).max(50), + paint: z.number().int().min(0).max(10000), + gear: z.array(z.string()), +}); + +const teamInfoSchema = z.object({ + score: z.number().int().min(0).max(100), + players: z.array(playerSchema), +}); + +export const detailedMapSchema = z.array( + z.object({ + stage: z.enum(stages), + mode: z.enum(modesShort as [string, ...string[]]), + duration: z.number().int().min(15).max(500), + winners: teamInfoSchema, + losers: teamInfoSchema, + date: z.string().refine((val) => { + const d = new Date(Number(val)); + if (Number.isNaN(d.getTime())) { + return false; + } + + const nd = new Date(); + nd.setMonth(-6); + + if (d.getTime() < nd.getTime()) { + return false; + } + + return true; + }), + }) +); + +const matchDetailsSchema = z.object({ + token: z.string(), + data: z.object({ + matchId: z.string().uuid(), + maps: z.array(detailedMapSchema), + }), +}); + +export const action: ActionFunction = async ({ request }) => { + const data = await parseRequestFormData({ + request, + schema: matchDetailsSchema, + useBody: true, + }); + + if (data.token !== process.env.LANISTA_TOKEN) { + return new Response(null, { status: 401 }); + } + + return new Response(null, { status: 204 }); +}; diff --git a/app/utils/index.ts b/app/utils/index.ts index 0ec976e12..c07e8823e 100644 --- a/app/utils/index.ts +++ b/app/utils/index.ts @@ -89,15 +89,18 @@ export function listToUserReadableString(input: string[]): string { export async function parseRequestFormData({ request, schema, + useBody = false, }: { request: Request; schema: T; + useBody?: boolean; }): Promise> { try { - const formDataObj = Object.fromEntries(await request.formData()); // False alarm // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return schema.parse(formDataObj); + return schema.parse( + useBody ? request.body : Object.fromEntries(await request.formData()) + ); } catch (e) { if (e instanceof z.ZodError) { console.error(e);