tg-tracker / src /lib /api.ts
iq7se2's picture
Upload 102 files
e03f82d verified
Raw History Blame Contribute Delete
7.7 kB
// 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<T>(res: Response): Promise<T> {
if (!res.ok) {
const t = await res.text().catch(() => "");
throw new Error(t || `HTTP ${res.status}`);
}
return res.json() as Promise<T>;
}
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<PageConfig>) =>
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<PageConfig>) =>
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<AppSettings>) =>
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<any>(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)),
};