import { ApiError } from "./errors"; import { requestJson } from "./http"; import type { CreateRacePayload, Race, RacesQuery, UpdateRacePayload } from "./types"; function isString(value: unknown): value is string { return typeof value === "string"; } function isNullableString(value: unknown): value is string | null { return value === null || typeof value === "string"; } function isOptionalNullableString(value: unknown): value is string | null | undefined { return value === undefined || isNullableString(value); } function normalizeRace(input: unknown): Race { const race = input as Partial; const isValid = isString(race?.id) && isString(race?.date) && isString(race?.title) && typeof race?.distanceKm === "number" && (race?.status === null || race?.status === "planned" || race?.status === "registered" || race?.status === "completed") && isNullableString(race?.officialUrl) && isOptionalNullableString(race?.coverImageUrl) && isNullableString(race?.startTime) && isNullableString(race?.clusterSchedule) && isNullableString(race?.bibPickup) && isNullableString(race?.bibNumber) && isNullableString(race?.finishTime) && isNullableString(race?.finishPlace) && isNullableString(race?.notes) && isString(race?.createdAt) && (race?.updatedAt === null || isString(race?.updatedAt)); if (!isValid) { throw new ApiError({ code: "unknown_error", status: null, message: "Некорректный формат данных от API.", }); } return { id: race.id, date: race.date, title: race.title, distanceKm: race.distanceKm, status: race.status, officialUrl: race.officialUrl, coverImageUrl: race.coverImageUrl ?? null, startTime: race.startTime, clusterSchedule: race.clusterSchedule, bibPickup: race.bibPickup, bibNumber: race.bibNumber, finishTime: race.finishTime, finishPlace: race.finishPlace, notes: race.notes, createdAt: race.createdAt, updatedAt: race.updatedAt, }; } function buildRacesQuery(query?: RacesQuery): string { if (!query) { return ""; } const params = new URLSearchParams(); if (typeof query.year === "number") { params.set("year", String(query.year)); } if (typeof query.month === "number") { params.set("month", String(query.month)); } const serialized = params.toString(); return serialized ? `?${serialized}` : ""; } export async function getRaces(query?: RacesQuery, init?: RequestInit): Promise { const response = await requestJson(`/races${buildRacesQuery(query)}`, init); if (!Array.isArray(response)) { throw new ApiError({ code: "unknown_error", status: null, message: "Некорректный формат списка забегов от API.", }); } return response.map(normalizeRace); } export async function getRaceById(id: string, init?: RequestInit): Promise { return normalizeRace(await requestJson(`/races/${id}`, init)); } export async function createRace(payload: CreateRacePayload): Promise { return normalizeRace( await requestJson("/races", { method: "POST", body: JSON.stringify(payload), }), ); } export async function updateRace(id: string, payload: UpdateRacePayload): Promise { return normalizeRace( await requestJson(`/races/${id}`, { method: "PATCH", body: JSON.stringify(payload), }), ); } export async function deleteRace(id: string): Promise { await requestJson(`/races/${id}`, { method: "DELETE" }); }