spb/crypto-trade-bot
Public
Python 54.7%
TypeScript 42.5%
SQL 1.9%
1export class ApiError extends Error {2 status: number;3 detail: unknown;4 constructor(status: number, detail: unknown) {5 super(typeof detail === "string" ? detail : `HTTP ${status}`);6 this.status = status;7 this.detail = detail;8 }9}1011function redirectToLogin() {12 if (typeof window !== "undefined" && !window.location.pathname.startsWith("/login")) {13 // Full navigation on purpose: clears all client state/query cache after the session expired.14 // eslint-disable-next-line @next/next/no-location-assign-relative-destination15 window.location.href = `/login?next=${encodeURIComponent(window.location.pathname)}`;16 }17}1819export async function api<T>(path: string, init?: RequestInit & { json?: unknown }): Promise<T> {20 const headers: Record<string, string> = { Accept: "application/json", ...(init?.headers as Record<string, string>) };21 let body = init?.body;22 if (init?.json !== undefined) {23 headers["Content-Type"] = "application/json";24 body = JSON.stringify(init.json);25 }26 const res = await fetch(path, { ...init, body, headers, credentials: "include" });27 if (res.status === 401) {28 redirectToLogin();29 throw new ApiError(401, "authentication required");30 }31 const text = await res.text();32 let data: unknown = text;33 try {34 data = text ? JSON.parse(text) : null;35 } catch {36 /* non-JSON */37 }38 if (!res.ok) {39 const detail = (data as { detail?: unknown })?.detail ?? data;40 throw new ApiError(res.status, typeof detail === "string" ? detail : JSON.stringify(detail));41 }42 return data as T;43}4445export const qs = (params: Record<string, string | number | boolean | undefined | null>) =>46 Object.entries(params)47 .filter(([, v]) => v !== undefined && v !== null && v !== "")48 .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)49 .join("&");50