// Typed API client helpers for the dashboard. export interface Pattern { id: string; name: string; expression: string; regex: string; description: string | null; enabled: boolean; createdAt: string; updatedAt: string; } export type PatternMode = "all" | "specific" | "none"; export type SaleType = "auction" | "sale"; export type ImageMode = "none" | "generated" | "url"; export type ImageTemplate = "default" | "minimal" | "telegram"; export interface PageConfig { id: string; name: string; recipient: string; enabled: boolean; patternMode: PatternMode; patternIds: string[]; minBidTon: number; maxBidTon: number; sources: string[]; saleTypes: SaleType[]; imageMode: ImageMode; imageTemplate: ImageTemplate; imageUrl?: string; messageTemplate: string; lastRunAt?: string | null; lastNotified: number; createdAt: string; } export interface AppSettings { botToken: string; intervalSeconds: number; realtimeMode: boolean; minBidTon: number; maxBidTon: number; sourceFragment: boolean; sourceGetgems: boolean; sourceMock: boolean; fetchSaleType: "auction" | "sale" | "both"; messageTemplate: string; } export interface Auction { id: string; username: string; source: string; saleType: string; bidAmount: number; currency: string; bidder: string | null; endTime: string | null; url: string | null; fetchedAt: string; matched: boolean; notified: boolean; patternId: string | null; pattern: Pattern | null; } export interface Notification { id: string; auctionId: string | null; username: string; pattern: string | null; bidAmount: number; source: string; saleType: string; recipient: string; pageId: string | null; pageName: string | null; status: string; message: string; hasImage: boolean; error: string | null; sentAt: string; } export interface MonitorStatus { running: boolean; enabled: boolean; lastRunAt: string | null; nextRunAt: string | null; lastRunDurationMs: number | null; lastError: string | null; lastRunStats: { fetched: number; matched: number; notified: number; errors: number } | null; intervalSeconds: number; enabledPageCount: number; } async function parseJson(res: Response): Promise { if (!res.ok) { const t = await res.text().catch(() => ""); throw new Error(t || `HTTP ${res.status}`); } return res.json() as Promise; } export const api = { patterns: { list: () => fetch("/api/patterns").then((r) => parseJson<{ patterns: Pattern[] }>(r)), create: (data: { name?: string; expression: string; description?: string; enabled?: boolean }) => fetch("/api/patterns", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }).then((r) => parseJson<{ pattern: Pattern }>(r)), remove: (id: string) => fetch(`/api/patterns/${id}`, { method: "DELETE" }).then((r) => parseJson<{ ok: boolean }>(r)), patch: (id: string, data: { enabled?: boolean; name?: string; description?: string }) => fetch(`/api/patterns/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }).then((r) => parseJson<{ pattern: Pattern }>(r)), preview: (expression: string) => fetch("/api/patterns/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "preview", expression }) }).then((r) => parseJson<{ ok: boolean; regex: string; length: number; variables: number; examples: string[] } | { error: string }>(r)), match: (username: string) => fetch("/api/patterns/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "match", username }) }).then((r) => parseJson<{ ok: boolean; match: boolean; matches: { id: string; name: string; expression: string }[] }>(r)), }, pages: { list: () => fetch("/api/pages").then((r) => parseJson<{ pages: PageConfig[] }>(r)), create: (data: Partial) => fetch("/api/pages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }).then((r) => parseJson<{ page: PageConfig }>(r)), update: (id: string, data: Partial) => fetch(`/api/pages/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }).then((r) => parseJson<{ page: PageConfig }>(r)), remove: (id: string) => fetch(`/api/pages/${id}`, { method: "DELETE" }).then((r) => parseJson<{ ok: boolean }>(r)), start: (id: string) => fetch(`/api/pages/${id}/start`, { method: "POST" }).then((r) => parseJson<{ ok: boolean; page: PageConfig }>(r)), stop: (id: string) => fetch(`/api/pages/${id}/stop`, { method: "POST" }).then((r) => parseJson<{ ok: boolean; page: PageConfig }>(r)), test: (id: string) => fetch(`/api/pages/${id}/test`, { method: "POST" }).then((r) => parseJson<{ ok: boolean; error?: string; hasImage?: boolean }>(r)), }, settings: { get: () => fetch("/api/settings").then((r) => parseJson<{ settings: AppSettings }>(r)), update: (data: Partial) => fetch("/api/settings", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }).then((r) => parseJson<{ settings: AppSettings }>(r)), }, auctions: (params: { matched?: boolean; source?: string; saleType?: string; limit?: number; username?: string } = {}) => { const q = new URLSearchParams(); if (params.matched !== undefined) q.set("matched", String(params.matched)); if (params.source) q.set("source", params.source); if (params.saleType) q.set("saleType", params.saleType); if (params.limit) q.set("limit", String(params.limit)); if (params.username) q.set("username", params.username); return fetch(`/api/auctions?${q.toString()}`).then((r) => parseJson<{ auctions: Auction[]; count: number }>(r)); }, notifications: (params: { limit?: number; status?: string } = {}) => { const q = new URLSearchParams(); if (params.limit) q.set("limit", String(params.limit)); if (params.status) q.set("status", params.status); return fetch(`/api/notifications?${q.toString()}`).then((r) => parseJson<{ notifications: Notification[]; count: number }>(r)); }, monitor: { status: () => fetch("/api/monitor/status").then((r) => parseJson<{ status: MonitorStatus }>(r)), run: () => fetch("/api/monitor/run", { method: "POST" }).then((r) => parseJson<{ result: { fetched: number; matched: number; notified: number; errors: number; error?: string }; status: MonitorStatus }>(r)), }, telegram: { test: (mode: "ping" | "broadcast", chatId?: string, withImage?: boolean) => fetch("/api/telegram/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode, chatId, withImage }) }).then((r) => parseJson(r)), sendCustom: (data: { recipient: string; text: string; imageUrl?: string }) => fetch("/api/telegram/send-custom", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }).then((r) => parseJson<{ ok: boolean; error?: string; messageId?: number }>(r)), }, search: (data: { expression?: string; minBid?: number; maxBid?: number; saleType?: string; hasDigits?: string; hasUnderscore?: string; minLen?: number; maxLen?: number; source?: string }) => fetch("/api/search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }).then((r) => parseJson<{ results: Auction[]; count: number } | { error: string }>(r)), seed: () => fetch("/api/seed", { method: "POST" }).then((r) => parseJson<{ ok: boolean; seeded: number }>(r)), };