SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
3.0 KB · 56 lines typescript
Raw Blame History
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