/** * Delivery Platform API client — Torbat Driver (real backend only). * Browser → /api/delivery BFF; SSR → direct upstream :8007 */ import { getValidAccessToken, refreshSession } from "@/lib/auth"; import type { CapabilitiesResponse, DriverListResponse, HealthResponse, ListParams, MetricsResponse, PaginatedList, PermissionsCatalogResponse, } from "@/modules/delivery/types"; const DELIVERY_BASE = typeof window !== "undefined" ? "/api/delivery" : process.env.DELIVERY_SERVICE_URL || process.env.NEXT_PUBLIC_DELIVERY_API_URL || "http://localhost:8007"; export class DeliveryApiError extends Error { constructor( public status: number, public code: string, message: string ) { super(message); this.name = "DeliveryApiError"; } } type RequestOptions = RequestInit & { tenantId: string; auth?: boolean }; async function request(path: string, options: RequestOptions): Promise { const { tenantId, auth = true, headers: customHeaders, ...init } = options; const headers = new Headers(customHeaders); if (!headers.has("Content-Type") && init.body) { headers.set("Content-Type", "application/json"); } headers.set("X-Tenant-ID", tenantId); if (auth) { const token = await getValidAccessToken(); if (token) headers.set("Authorization", `Bearer ${token}`); } const url = path.startsWith("http") ? path : `${DELIVERY_BASE.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`; let res: Response; try { res = await fetch(url, { ...init, headers }); } catch (err) { const detail = err instanceof Error ? err.message : "network_error"; throw new Error(`اتصال به سرویس تربت درایور برقرار نشد (${url}) — ${detail}`); } if (res.status === 401 && auth) { const refreshed = await refreshSession({ force: true }); if (refreshed) { headers.set("Authorization", `Bearer ${refreshed}`); res = await fetch(url, { ...init, headers }); } } if (!res.ok) { const body = await res.json().catch(() => ({})); throw new DeliveryApiError( res.status, body?.error?.code || body?.detail || "unknown_error", body?.error?.message || body?.detail || res.statusText ); } if (res.status === 204) return undefined as T; return res.json() as Promise; } function qs(params?: Record) { if (!params) return ""; const sp = new URLSearchParams(); Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== "") sp.set(k, String(v)); }); const s = sp.toString(); return s ? `?${s}` : ""; } function crudResource(basePath: string) { return { list: (tenantId: string, params?: ListParams) => request[]>(`${basePath}${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`${basePath}/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(basePath, { tenantId, method: "POST", body: JSON.stringify(body), }), update: (tenantId: string, id: string, body: Record) => request>(`${basePath}/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), remove: (tenantId: string, id: string) => request>(`${basePath}/${id}/delete`, { tenantId, method: "POST", }), }; } export const deliveryApi = { health: (): Promise => fetch(`${DELIVERY_BASE}/health`).then((r) => r.json()), capabilities: (): Promise => fetch(`${DELIVERY_BASE}/capabilities`).then((r) => r.json()), metrics: (): Promise => fetch(`${DELIVERY_BASE}/metrics`).then((r) => r.json()), permissions: { catalog: (tenantId: string) => request(`/api/v1/permissions/catalog`, { tenantId }), }, organizations: crudResource("/api/v1/organizations"), hubs: crudResource("/api/v1/hubs"), externalProviders: crudResource("/api/v1/external-providers"), routingEngines: crudResource("/api/v1/routing-engines"), configurations: crudResource("/api/v1/configurations"), settings: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/settings${qs(params)}`, { tenantId }), upsert: (tenantId: string, body: Record) => request>(`/api/v1/settings`, { tenantId, method: "PUT", body: JSON.stringify(body), }), }, audit: { list: ( tenantId: string, params: { entity_type: string; entity_id: string; limit?: number } ) => request[]>(`/api/v1/audit${qs(params)}`, { tenantId }), }, drivers: { list: (tenantId: string, params?: ListParams) => request[]>( `/api/v1/drivers${qs(params)}`, { tenantId } ).then(normalizeDriverList), get: (tenantId: string, id: string) => request>(`/api/v1/drivers/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/drivers`, { tenantId, method: "POST", body: JSON.stringify(body), }), update: (tenantId: string, id: string, body: Record) => request>(`/api/v1/drivers/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), remove: (tenantId: string, id: string) => request>(`/api/v1/drivers/${id}/delete`, { tenantId, method: "POST", }), activate: (tenantId: string, id: string, body: Record) => lifecycleAction(tenantId, id, "activate", body), suspend: (tenantId: string, id: string, body: Record) => lifecycleAction(tenantId, id, "suspend", body), resume: (tenantId: string, id: string, body: Record) => lifecycleAction(tenantId, id, "resume", body), deactivate: (tenantId: string, id: string, body: Record) => lifecycleAction(tenantId, id, "deactivate", body), block: (tenantId: string, id: string, body: Record) => lifecycleAction(tenantId, id, "block", body), unblock: (tenantId: string, id: string, body: Record) => lifecycleAction(tenantId, id, "unblock", body), archive: (tenantId: string, id: string, body: Record) => lifecycleAction(tenantId, id, "archive", body), lifecycle: (tenantId: string, id: string) => request[]>(`/api/v1/drivers/${id}/lifecycle`, { tenantId }), credentials: { list: (tenantId: string, driverId: string) => request[]>(`/api/v1/drivers/${driverId}/credentials`, { tenantId, }), create: (tenantId: string, driverId: string, body: Record) => request>(`/api/v1/drivers/${driverId}/credentials`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, documents: { list: (tenantId: string, driverId: string) => request[]>(`/api/v1/drivers/${driverId}/documents`, { tenantId }), create: (tenantId: string, driverId: string, body: Record) => request>(`/api/v1/drivers/${driverId}/documents`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, }, }; function lifecycleAction( tenantId: string, id: string, action: string, body: Record ) { return request>(`/api/v1/drivers/${id}/${action}`, { tenantId, method: "POST", body: JSON.stringify(body), }); } function normalizeDriverList( data: DriverListResponse | Record[] ): PaginatedList> { if (Array.isArray(data)) { return { items: data, total: data.length, page: 1, page_size: data.length }; } return { items: data.items ?? [], total: data.total ?? 0, page: data.page ?? 1, page_size: data.page_size ?? 20, }; }