50 lines
4.2 KiB
TypeScript
50 lines
4.2 KiB
TypeScript
export type Activity = {
|
|
spot_id: string; waterbody_slug: string; waterbody: string; fish_slug: string;
|
|
fish: string; x: number; y: number; best_bait: string | null; catches: number;
|
|
unique_players: number; average_weight_g: number; max_weight_g: number;
|
|
last_confirmed_at: string; activity_score: number; confidence_score: number;
|
|
explanation: string; sources: string[];
|
|
coordinate_precision: "exact" | "approximate" | "area" | "missing"; coordinate_sources: string[];
|
|
};
|
|
|
|
export type PaginatedActivity = {
|
|
items: Activity[];
|
|
total: number;
|
|
limit: number;
|
|
offset: number;
|
|
};
|
|
|
|
export type Spot = { id: string; waterbody_slug: string; waterbody: string; x: number; y: number; description: string | null; catches_24h: number; catches_3d: number; catches_7d: number; top_baits: string[]; coordinate_precision: "exact" | "approximate" | "area" | "missing"; coordinate_sources: string[] };
|
|
export type Catch = { id: string; fish: string; weight_g: number; bait: string | null; player_name: string | null; caught_at: string | null; reported_at: string; retrieve_method: string | null; retrieve_speed: number | null; source_system: string; source_url: string | null };
|
|
export type DictionaryItem = { id: string; slug: string; name_ru: string; unlock_level?: number | null; fish_species_count?: number | null; source_system?: string | null; source_external_id?: string | null; source_url?: string | null; description?: string | null; source_aliases?: string[] | null; source_fish_species?: string[] | null; source_image_urls?: string[] | null; source_point_urls?: string[] | null; source_checked_at?: string | null };
|
|
export type TackleItem = { id: string; name: string; category: string; subcategory: string | null; brand: string | null; family: string | null; unlock_level: number | null; source_system: string | null; source_external_id: string | null; source_url: string | null; source_checked_at: string | null; missing_fields: string[] };
|
|
export type PaginatedTackleItems = { items: TackleItem[]; total: number; limit: number; offset: number };
|
|
export type OfficialRecord = { id: string; fish: string; weight_g: number; waterbody: string; bait: string | null; player_name: string | null; record_date: string | null; category: string | null; region: string | null; source_url: string | null; source_system: string };
|
|
export type PaginatedOfficialRecord = {
|
|
items: OfficialRecord[];
|
|
total: number;
|
|
limit: number;
|
|
offset: number;
|
|
};
|
|
export type PublicObservation = { id: string; source_system: string; source_name: string; source_url: string; fish_name: string; waterbody_name: string; x: number | null; y: number | null; weight_g: number | null; last_seen_at: string; missing_fields: string[]; quality: "incomplete" | "unverified" };
|
|
export type ImportRun = { id: string; started_at: string; finished_at: string | null; status: string; source_url: string; rows_seen: number; rows_created: number; rows_updated: number; error_summary: string | null };
|
|
export type SourceStatus = { source_system: string; name: string; status: "healthy" | "stale" | "temporarily_limited" | "source_changed" | "waiting" | "disabled"; last_started_at: string | null; last_success_at: string | null; observations: number };
|
|
export type MediaVariant = { role: "card" | "detail"; format: "webp" | "avif"; width: number; height: number; url: string };
|
|
export type MediaAsset = { id: string; entity_type: "fish" | "waterbody" | "tackle" | "reference"; entity_key: string; label: string | null; width: number; height: number; content_type: string; image_url: string; source_system: string; source_url: string; variants?: MediaVariant[] };
|
|
|
|
export const spotPath = (item: Pick<Activity, "waterbody_slug" | "x" | "y">) => `/spots/${item.waterbody_slug}-${item.x}x${item.y}`;
|
|
|
|
export { activityLevel, ago, kg, plural } from "./presentation";
|
|
|
|
const base = process.env.API_INTERNAL_URL || import.meta.env.API_INTERNAL_URL || "http://localhost:8000";
|
|
|
|
export class ApiError extends Error {
|
|
constructor(public status: number) { super(`API ${status}`); }
|
|
}
|
|
|
|
export async function api<T>(path: string): Promise<T> {
|
|
const response = await fetch(`${base}${path}`, { signal: AbortSignal.timeout(8000) });
|
|
if (!response.ok) throw new ApiError(response.status);
|
|
return response.json() as Promise<T>;
|
|
}
|