const BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; let authToken: string | null = null; export function setAuthToken(token: string | null) { authToken = token; } async function request(path: string, options: RequestInit = {}): Promise { const headers: Record = { "Content-Type": "application/json", ...(options.headers as Record), }; if (authToken) { headers["Authorization"] = `Bearer ${authToken}`; } const res = await fetch(`${BASE}${path}`, { ...options, headers }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })); throw new Error(err.detail || `HTTP ${res.status}`); } return res.json(); } export const api = { login: (email: string, password: string) => request<{ access_token: string; role: string; tenant_id: string; full_name: string }>( "/api/v1/auth/login", { method: "POST", body: JSON.stringify({ email, password }) } ), me: () => request<{ id: string; email: string; full_name: string; role: string; tenant_id: string }>( "/api/v1/auth/me" ), dashboard: (tenantId: string) => request(`/api/v1/dashboard/${tenantId}`), findings: (tenantId: string, params?: string) => request(`/api/v1/findings?tenant_id=${tenantId}${params ? "&" + params : ""}`), finding: (id: string) => request(`/api/v1/findings/${id}`), updateFindingStatus: (id: string, body: { status: string; resolution_note?: string }) => request(`/api/v1/findings/${id}/status`, { method: "PATCH", body: JSON.stringify(body), }), attackPaths: (findingId: string) => request(`/api/v1/attack-paths/${findingId}`), footprint: (tenantId: string) => request(`/api/v1/footprint/${tenantId}`), aiExplain: (findingId: string, question: string) => request<{ question: string; answer: string }>(`/api/v1/ai/explain/${findingId}?question=${encodeURIComponent(question)}`), aiExplainFinding: (findingId: string) => request<{ finding_id: string; summary?: string; business_impact?: string; impact_level?: string; remediation_steps?: string; fix_priority?: string; generated_at?: string; status: "available" | "processing"; message?: string; }>(`/api/v1/findings/${findingId}/ai-explain`), }; // ─── Types ──────────────────────────────────────────────────────────────────── export interface DashboardData { tenant_name: string; current_score: number; previous_score: number | null; score_delta: number | null; score_trend: { date: string; score: number }[]; top_risks: RiskCard[]; open_critical: number; open_high: number; open_medium: number; total_open: number; baseline_score: number | null; baseline_date: string | null; } export interface RiskCard { id: string; title: string; ai_summary: string | null; ai_business_impact: string | null; ai_impact_level: string | null; ai_fix_priority: string | null; severity: string; category: string; } export interface Finding { id: string; tenant_id: string; title: string; severity: string; status: string; category: string; technical_description: string | null; cve_id: string | null; cvss_score: number | null; affected_component: string | null; ai_summary: string | null; ai_business_impact: string | null; ai_impact_level: string | null; ai_remediation_steps: string | null; ai_fix_priority: string | null; assignee_email: string | null; due_date: string | null; is_top_risk: boolean; source: string | null; created_at: string; updated_at: string; } export interface AttackPath { id: string; finding_id: string; title: string; ai_narrative: string | null; nodes_json: string | null; edges_json: string | null; } export interface FootprintData { tenant_id: string; executives: { id: string; name: string; title: string; email: string }[]; footprint_findings: any[]; total_exposures: number; }