const TOKEN_KEY = 'archnest_token' export function getToken(): string | null { return localStorage.getItem(TOKEN_KEY) } export function setToken(token: string | null) { if (token) localStorage.setItem(TOKEN_KEY, token) else localStorage.removeItem(TOKEN_KEY) } export class ApiError extends Error { status: number constructor(status: number, message: string) { super(message) this.status = status } } export async function apiFetch(path: string, options: RequestInit = {}): Promise { const token = getToken() const headers: Record = { ...(options.body ? { 'Content-Type': 'application/json' } : {}), ...(options.headers as Record | undefined), } if (token) headers.Authorization = `Bearer ${token}` const res = await fetch(`/api${path}`, { ...options, headers }) if (!res.ok) { let message = res.statusText try { const body = await res.json() message = body.error ?? message } catch { // ignore non-JSON error bodies } throw new ApiError(res.status, message) } if (res.status === 204) return undefined as T return res.json() as Promise } export const api = { getSetupStatus: () => apiFetch<{ needsSetup: boolean }>('/system/setup-status'), setup: (username: string, password: string) => apiFetch<{ token: string }>('/setup', { method: 'POST', body: JSON.stringify({ username, password }) }), login: (username: string, password: string) => apiFetch<{ token: string }>('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) }), me: () => apiFetch<{ user: { id: number; username: string; display_name: string | null; email: string | null; avatar_data_url: string | null } }>('/auth/me'), listIntegrations: () => apiFetch<{ integrations: Integration[] }>('/integrations'), createIntegration: (data: { type: string; name: string; config?: Record; secrets?: Record }) => apiFetch<{ integration: Integration }>('/integrations', { method: 'POST', body: JSON.stringify(data) }), updateIntegration: (id: number, data: Partial<{ name: string; config: Record; secrets: Record }>) => apiFetch<{ integration: Integration }>(`/integrations/${id}`, { method: 'PUT', body: JSON.stringify(data) }), deleteIntegration: (id: number) => apiFetch(`/integrations/${id}`, { method: 'DELETE' }), testIntegration: (id: number) => apiFetch<{ ok: boolean; message: string }>(`/integrations/${id}/test`, { method: 'POST' }), listBookmarks: () => apiFetch<{ bookmarks: Bookmark[] }>('/bookmarks'), listBookmarkCategories: () => apiFetch<{ categories: BookmarkCategory[] }>('/bookmarks/categories'), createBookmarkCategory: (data: { name: string; icon?: string; sortOrder?: number }) => apiFetch<{ id: number }>('/bookmarks/categories', { method: 'POST', body: JSON.stringify(data) }), createBookmark: (data: { categoryId?: number | null; title: string; url: string; icon?: string; favorite?: boolean }) => apiFetch<{ id: number }>('/bookmarks', { method: 'POST', body: JSON.stringify(data) }), updateBookmark: (id: number, data: Partial<{ categoryId: number | null; title: string; url: string; icon: string; favorite: boolean }>) => apiFetch<{ ok: boolean }>(`/bookmarks/${id}`, { method: 'PUT', body: JSON.stringify(data) }), deleteBookmark: (id: number) => apiFetch(`/bookmarks/${id}`, { method: 'DELETE' }), } export interface Integration { id: number type: string name: string enabled: boolean status: string config: Record lastCheckedAt: string | null createdAt: string } export interface Bookmark { id: number category_id: number | null title: string url: string icon: string | null favorite: number status: string last_checked_at: string | null created_at: string } export interface BookmarkCategory { id: number name: string icon: string | null sort_order: number }