import { z } from 'zod'; import { matchSchema, matchPlayerSchema } from './match.schema'; /** * Zod schemas for Player data models */ /** Player schema */ export const playerSchema = z.object({ id: z.string().min(1), // Steam ID uint64 as string to preserve precision name: z.string().min(1), avatar: z.string().url(), vanity_url: z.string().optional(), vanity_url_real: z.string().optional(), steam_updated: z.string().datetime().optional(), profile_created: z.string().datetime().optional(), wins: z.number().int().nonnegative().optional(), losses: z.number().int().nonnegative().optional(), // Also support backend's typo "looses" looses: z.number().int().nonnegative().optional(), ties: z.number().int().nonnegative().optional(), vac_count: z.number().int().nonnegative().optional(), vac_date: z.string().datetime().nullable().optional(), game_ban_count: z.number().int().nonnegative().optional(), game_ban_date: z.string().datetime().nullable().optional(), oldest_sharecode_seen: z.string().optional(), tracked: z.boolean().optional(), matches: z .array( matchSchema.extend({ stats: matchPlayerSchema }) ) .optional() }); /** Transform player data to normalize "looses" to "losses" */ export const normalizePlayerData = (data: z.infer) => { if (data.looses !== undefined && data.losses === undefined) { return { ...data, losses: data.looses }; } return data; }; /** PlayerMeta schema */ export const playerMetaSchema = z.object({ id: z.number().positive(), name: z.string().min(1), avatar: z.string().url(), recent_matches: z.number().int().nonnegative(), last_match_date: z.string().datetime(), avg_kills: z.number().nonnegative(), avg_deaths: z.number().nonnegative(), avg_kast: z.number().nonnegative(), win_rate: z.number().nonnegative() }); /** PlayerProfile schema (extended with calculated stats) */ export const playerProfileSchema = playerSchema.extend({ total_matches: z.number().int().nonnegative(), kd_ratio: z.number().nonnegative(), win_rate: z.number().nonnegative(), avg_headshot_pct: z.number().nonnegative(), avg_kast: z.number().nonnegative(), current_rating: z.number().int().min(0).max(30000).optional(), peak_rating: z.number().int().min(0).max(30000).optional() }); /** Parser functions */ export const parsePlayer = (data: unknown) => { const parsed = playerSchema.parse(data); return normalizePlayerData(parsed); }; export const parsePlayerSafe = (data: unknown) => { const result = playerSchema.safeParse(data); if (result.success) { return { ...result, data: normalizePlayerData(result.data) }; } return result; }; export const parsePlayerMeta = (data: unknown) => playerMetaSchema.parse(data); export const parsePlayerProfile = (data: unknown) => playerProfileSchema.parse(data); /** Infer TypeScript types */ export type PlayerSchema = z.infer; export type PlayerMetaSchema = z.infer; export type PlayerProfileSchema = z.infer;