import type { Agent, AdminUser, AuditLog, ChangeRequest, GlobalSettings, Notification, RequestState, User, } from "./types"; export class ApiError extends Error { status: number; data?: unknown; constructor(message: string, status: number, data?: unknown) { super(message); this.status = status; this.data = data; } } async function api(path: string, opts: RequestInit = {}): Promise { const res = await fetch(path, { credentials: "include", headers: opts.body && !(opts.headers && "Content-Type" in opts.headers) ? { "Content-Type": "application/json", ...(opts.headers || {}) } : opts.headers, ...opts, }); let body: any = null; try { body = await res.json(); } catch { /* no body */ } if (!res.ok || body?.status === "FAILED") { throw new ApiError(body?.message || `Request failed (${res.status})`, res.status, body?.data); } return (body?.data ?? body) as T; } // ── Auth ────────────────────────────────────────────────────────────────────── export const auth = { me: () => api("/api/auth/me"), login: (username: string, password: string, totp?: string) => api("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password, totp }) }), register: (username: string, display_name: string, password: string) => api("/api/auth/register", { method: "POST", body: JSON.stringify({ username, display_name, password }), }), logout: () => api("/api/auth/logout", { method: "POST" }), setup2fa: () => api<{ secret: string; otpauth_url: string }>("/api/auth/2fa/setup", { method: "POST" }), enable2fa: (totp: string) => api("/api/auth/2fa/enable", { method: "POST", body: JSON.stringify({ totp }) }), disable2fa: (password: string) => api("/api/auth/2fa/disable", { method: "POST", body: JSON.stringify({ password }) }), }; // ── Account ──────────────────────────────────────────────────────────────────── export const account = { updateProfile: (display_name: string) => api("/api/account/profile", { method: "PATCH", body: JSON.stringify({ display_name }) }), changePassword: (current_password: string, new_password: string) => api("/api/account/password", { method: "POST", body: JSON.stringify({ current_password, new_password }), }), updateSettings: (settings: { auto_delete_enabled?: boolean; auto_delete_days?: number }) => api<{ auto_delete_enabled: boolean; auto_delete_days: number }>("/api/account/settings", { method: "PATCH", body: JSON.stringify(settings), }), deleteAccount: (password: string) => api("/api/account", { method: "DELETE", body: JSON.stringify({ password, confirm: "DELETE" }) }), exportUrl: "/api/account/export", }; // ── Agents ───────────────────────────────────────────────────────────────────── export const agents = { list: () => api("/api/agents"), create: (input: Partial) => api("/api/agents", { method: "POST", body: JSON.stringify(input) }), update: (id: number, input: Partial) => api(`/api/agents/${id}`, { method: "PATCH", body: JSON.stringify(input) }), regenerateKey: (id: number) => api(`/api/agents/${id}/regenerate-key`, { method: "POST" }), setDisabled: (id: number, disabled: boolean) => api(`/api/agents/${id}/disabled`, { method: "POST", body: JSON.stringify({ disabled }) }), remove: (id: number) => api(`/api/agents/${id}`, { method: "DELETE" }), }; // ── Change requests (human) ────────────────────────────────────────────────────── export const requests = { list: (params: { page?: number; page_size?: number; state?: string; agent_id?: number } = {}) => { const q = new URLSearchParams(); if (params.page) q.set("page", String(params.page)); if (params.page_size) q.set("page_size", String(params.page_size)); if (params.state) q.set("state", params.state); if (params.agent_id) q.set("agent_id", String(params.agent_id)); return api<{ page: number; page_size: number; total: number; requests: ChangeRequest[] }>( `/api/change-requests?${q.toString()}`, ); }, summary: () => api<{ counts: Record }>("/api/change-requests/summary"), get: (id: string) => api(`/api/change-requests/${id}`), decide: (id: string, decision: "APPROVE" | "REJECT" | "REQUEST_CHANGES", comment?: string) => api(`/api/change-requests/${id}/decision`, { method: "POST", body: JSON.stringify({ decision, comment }), }), }; // ── Notifications ──────────────────────────────────────────────────────────────── export const notifications = { list: (params: { page?: number; page_size?: number; unread?: boolean } = {}) => { const q = new URLSearchParams(); if (params.page) q.set("page", String(params.page)); if (params.page_size) q.set("page_size", String(params.page_size)); if (params.unread) q.set("unread", "true"); return api<{ total: number; unread_count: number; notifications: Notification[] }>( `/api/notifications?${q.toString()}`, ); }, markRead: (id: number) => api(`/api/notifications/${id}/read`, { method: "POST" }), markAllRead: () => api("/api/notifications/read-all", { method: "POST" }), clear: () => api("/api/notifications", { method: "DELETE" }), }; // ── Global ─────────────────────────────────────────────────────────────────────── export const global = { get: () => api("/api/global"), }; // ── Admin ───────────────────────────────────────────────────────────────────────── export const admin = { users: () => api("/api/admin/users"), updateUser: (id: number, input: { role?: "ADMIN" | "USER"; disabled?: boolean }) => api(`/api/admin/users/${id}`, { method: "PATCH", body: JSON.stringify(input) }), deleteUser: (id: number) => api(`/api/admin/users/${id}`, { method: "DELETE" }), settings: () => api("/api/admin/settings"), updateSettings: (input: { registration_enabled?: boolean; requests_enabled?: boolean }) => api("/api/admin/settings", { method: "PATCH", body: JSON.stringify(input) }), agents: () => api<(Agent & { owner: { id: number; username: string } })[]>("/api/admin/agents"), setAgentDisabled: (id: number, disabled: boolean) => api(`/api/admin/agents/${id}/disabled`, { method: "POST", body: JSON.stringify({ disabled }) }), deleteAgent: (id: number) => api(`/api/admin/agents/${id}`, { method: "DELETE" }), auditLogs: (page = 1) => api<{ total: number; page: number; page_size: number; logs: AuditLog[] }>( `/api/admin/audit-logs?page=${page}`, ), };