diff --git a/src/app/[locale]/tournaments/page.tsx b/src/app/[locale]/tournaments/page.tsx index 95c78f4..95ac6da 100644 --- a/src/app/[locale]/tournaments/page.tsx +++ b/src/app/[locale]/tournaments/page.tsx @@ -3,7 +3,7 @@ import { groupBy } from "lodash"; import clientPromise from "@/lib/mongodb"; import { TournamentData } from "@/types"; -import { TimeControl, Tournament, tcMap } from "@/types"; +import { TimeControl, Tournament, tcMap, tournamentDataSchema } from "@/types"; import { errorLog } from "@/utils/logger"; import TournamentsDisplay from "./TournamentsDisplay"; @@ -43,9 +43,45 @@ const getTournaments = async () => { ]) .toArray(); + const bad = data.filter((t) => { + const result = tournamentDataSchema.safeParse(t); + if (result.success === false) { + console.log(JSON.stringify(result, null, 2)); + console.log(JSON.stringify(t, null, 2)); + + if (typeof process.env.DISCORD_WEBHOOK_ERROR_LOGS_URL === "string") { + fetch(process.env.DISCORD_WEBHOOK_ERROR_LOGS_URL as string, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + embeds: [ + { + title: "Error parsing tournaments", + fields: [ + { + name: "parseError", + value: JSON.stringify(result, null, 2), + }, + { name: "tournament", value: JSON.stringify(t, null, 2) }, + ], + }, + ], + }), + }); + } + } + + return result.success === false; + }); + + const badIds = bad.map((t) => t._id.toString()); + const goodData = data.filter((t) => !badIds.includes(t._id.toString())); + // Group the tournaments by their location const groupedByLocation = groupBy( - data, + goodData, (t) => `${t.coordinates[0]}_${t.coordinates[1]}`, ); @@ -73,7 +109,7 @@ const getTournaments = async () => { dateRangesByLocation[location] = dateRanges; } - return data.map((t) => { + return goodData.map((t) => { const location = `${t.coordinates[0]}_${t.coordinates[1]}`; const date = parse(t.date, "dd/MM/yyyy", new Date()); const dateRanges = dateRangesByLocation[location]; @@ -97,8 +133,8 @@ const getTournaments = async () => { url: t.url, timeControl, latLng: { lat: t.coordinates[0], lng: t.coordinates[1] }, - norm: t.norm_tournament, - pending: t.pending, + norm: t.norm_tournament ?? false, + pending: t.pending ?? false, status: t.status, }; }); diff --git a/src/types.ts b/src/types.ts index 9f9aaac..66af9b0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,23 +1,27 @@ import { LatLngLiteral } from "leaflet"; import { ObjectId } from "mongodb"; +import { z } from "zod"; -export type Status = "scheduled" | "ongoing" | "finished"; +export type Status = "scheduled" | "ongoing" | "finished" | "in-play"; -export type TournamentData = { +export const tournamentDataSchema = z.object({ + tournament_id: z.string(), + town: z.string(), + department: z.string(), + country: z.string(), + tournament: z.string(), + url: z.string(), + time_control: z.string(), + norm_tournament: z.boolean().optional(), + date: z.string(), + coordinates: z.array(z.number()).min(2).max(2), + entry_method: z.enum(["manual", "auto"]), + pending: z.boolean().optional(), + status: z.enum(["scheduled", "ongoing", "finished", "in-play"]), +}); + +export type TournamentData = z.infer & { _id: ObjectId; - tournament_id: string; - town: string; - department: string; - country: string; - tournament: string; - url: string; - time_control: "Cadence Lente" | "Rapide" | "Blitz" | string; - norm_tournament: boolean; - date: string; - coordinates: [number, number]; - entry_method: "manual" | "auto"; - pending: boolean; - status: Status; }; export type ClubData = {