// Client for the arcadia-search admin sidecar (`/admin/*` on the // search box, default :7801). Used by the Search route to manage // tenants, corpora, and trigger rebuilds. // // Auth: static bearer token from VITE_ARCADIA_SEARCH_ADMIN_TOKEN, // matched constant-time against ADMIN_TOKEN on the sidecar. The token // ships in the client bundle — fine for an internal admin tool on a // trusted network; in production, proxy through arcadia-core. const BASE_URL = import.meta.env.VITE_ARCADIA_SEARCH_ADMIN_URL ?? "http://127.0.0.1:7801" const TOKEN = import.meta.env.VITE_ARCADIA_SEARCH_ADMIN_TOKEN ?? "" export class SearchAdminError extends Error { status: number constructor(message: string, status: number) { super(message) this.name = "SearchAdminError" this.status = status } } async function call( method: "GET" | "POST" | "PUT" | "DELETE", path: string, body?: unknown, ): Promise { const headers: Record = {} if (TOKEN) headers["Authorization"] = `Bearer ${TOKEN}` if (body !== undefined) headers["Content-Type"] = "application/json" const res = await fetch(`${BASE_URL}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), }) if (!res.ok) { const text = await res.text().catch(() => "") throw new SearchAdminError( text || `${res.status} ${res.statusText}`, res.status, ) } if (res.status === 204) return undefined as T return (await res.json()) as T } export type TenantSummary = { id: string; corpus_count: number } export type CorpusSummary = { tenant: string corpus: string indexed: boolean num_docs: number | null live_path: string | null } export type CorpusDetail = { config: Record status: CorpusSummary } export type RebuildResult = { tenant: string corpus: string chunk_count: number live_path: string built_at: string } export const searchAdmin = { baseUrl: BASE_URL, hasToken: !!TOKEN, listTenants: () => call<{ tenants: TenantSummary[] }>("GET", "/admin/tenants"), createTenant: (id: string) => call("POST", "/admin/tenants", { id }), deleteTenant: (id: string) => call("DELETE", `/admin/tenants/${encodeURIComponent(id)}`), listCorpora: (tenant: string) => call<{ corpora: CorpusSummary[] }>( "GET", `/admin/tenants/${encodeURIComponent(tenant)}/corpora`, ), createCorpus: (tenant: string, body: Record) => call( "POST", `/admin/tenants/${encodeURIComponent(tenant)}/corpora`, body, ), getCorpus: (tenant: string, corpus: string) => call( "GET", `/admin/tenants/${encodeURIComponent(tenant)}/corpora/${encodeURIComponent(corpus)}`, ), updateCorpus: ( tenant: string, corpus: string, body: Record, ) => call( "PUT", `/admin/tenants/${encodeURIComponent(tenant)}/corpora/${encodeURIComponent(corpus)}`, body, ), deleteCorpus: (tenant: string, corpus: string) => call( "DELETE", `/admin/tenants/${encodeURIComponent(tenant)}/corpora/${encodeURIComponent(corpus)}`, ), rebuild: (tenant: string, corpus: string) => call( "POST", `/admin/tenants/${encodeURIComponent(tenant)}/corpora/${encodeURIComponent(corpus)}/rebuild`, ), restart: () => call("POST", "/admin/restart"), }