spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1import 'server-only';2import { ApiError, API_URL } from './api';3import { adminToken } from './admin-auth';4import type { Problem } from './types';5import type { AdminCoverageResponse, AdminIssuesResponse, AdminOverview, AdminRawResponse, AdminRunsResponse } from './types-explore';67/**8 * Admin endpoints (`X-Admin-Token`). Server components / actions only; never cached. `ADMIN_API_URL` lets9 * the panel target a second API instance (e.g. one started with a token while the main one has none).10 */11const ADMIN_BASE = `${(process.env.ADMIN_API_URL ?? API_URL).replace(/\/$/, '')}/api/v1/admin`;1213type Query = Record<string, string | number | null | undefined>;1415export async function adminRequest<T>(path: string, query?: Query, init: { method?: 'GET' | 'POST' } = {}): Promise<T> {16 const token = adminToken();17 if (!token) throw new ApiError(503, path, { title: 'Admin disabled', status: 503, detail: 'CA_ADMIN_TOKEN is not configured on the web server.' });18 const p = new URLSearchParams();19 for (const [k, v] of Object.entries(query ?? {})) if (v !== undefined && v !== null && v !== '') p.set(k, String(v));20 const qs = p.toString();21 const url = `${ADMIN_BASE}${path}${qs ? `?${qs}` : ''}`;22 let res: Response;23 try {24 res = await fetch(url, { method: init.method ?? 'GET', headers: { accept: 'application/json', 'X-Admin-Token': token }, cache: 'no-store' });25 } catch (e) {26 throw new ApiError(0, path, null, `Admin API unreachable at ${ADMIN_BASE} (${(e as Error).message})`);27 }28 if (!res.ok) {29 let problem: Problem | null = null;30 try {31 const body = (await res.json()) as unknown;32 if (body && typeof body === 'object' && 'status' in body && 'title' in body) problem = body as Problem;33 } catch {34 /* non-JSON */35 }36 throw new ApiError(res.status, path, problem);37 }38 return (await res.json()) as T;39}4041export const adminApi = {42 overview: () => adminRequest<AdminOverview>('/overview'),43 runs: (q: { limit?: number; connector?: string; status?: string } = {}) => adminRequest<AdminRunsResponse>('/runs', { limit: 200, ...q }),44 issues: (q: { limit?: number; severity?: string; connector?: string; indicator?: string; code?: string; run_id?: string } = {}) => adminRequest<AdminIssuesResponse>('/issues', { limit: 300, ...q }),45 coverage: () => adminRequest<AdminCoverageResponse>('/coverage'),46 raw: (runId: string) => adminRequest<AdminRawResponse>('/raw', { run_id: runId }),47 refresh: () => adminRequest<{ ok: boolean; pid: number; signal: string; sent_at: string }>('/refresh', undefined, { method: 'POST' }),48 clearCache: () => adminRequest<{ ok: boolean; before: Record<string, number> }>('/cache/clear', undefined, { method: 'POST' }),49};5051/** Turn an admin fetch failure into a short message for the UI. */52export function adminErrorMessage(e: unknown): string {53 if (e instanceof ApiError) return `${e.status || 'network'} — ${e.problem?.detail ?? e.problem?.title ?? e.message}`;54 return e instanceof Error ? e.message : String(e);55}56