import { getValidAccessToken, refreshSession } from "@/lib/auth"; import type { CapabilitiesResponse, ContentDetail, DashboardResponse, HealthResponse, ProjectDetail, WorkspaceRecord, } from "@/modules/workspace/types"; const WORKSPACE_BASE = typeof window !== "undefined" ? "/api/workspace" : process.env.WORKSPACE_SERVICE_URL || process.env.NEXT_PUBLIC_WORKSPACE_API_URL || "http://localhost:8013"; export class WorkspaceApiError extends Error { constructor( public status: number, public code: string, message: string, public details?: unknown ) { super(message); this.name = "WorkspaceApiError"; } } type RequestOptions = RequestInit & { tenantId: string; auth?: boolean }; function qs(params?: Record) { const search = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { if (value) search.set(key, value); }); const text = search.toString(); return text ? `?${text}` : ""; } 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 : `${WORKSPACE_BASE.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`; let res: Response; try { res = await fetch(url, { ...init, headers, cache: "no-store" }); } catch (err) { const detail = err instanceof Error ? err.message : "network_error"; throw new Error(`اتصال به فضای کاری برقرار نشد — ${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, cache: "no-store" }); } } if (!res.ok) { const body = await res.json().catch(() => ({})); const detail = body?.error ?? body?.detail ?? {}; const message = detail?.message || (typeof detail === "string" ? detail : null) || (res.status === 409 ? "این تغییر وضعیت مجاز نیست." : res.statusText); throw new WorkspaceApiError( res.status, detail?.code || (typeof detail === "string" ? detail : "unknown_error"), message, detail ); } if (res.status === 204) return undefined as T; return res.json() as Promise; } export const workspaceApi = { health: (): Promise => fetch(`${WORKSPACE_BASE}/health`).then((r) => r.json()), capabilities: (tenantId: string) => request("/capabilities", { tenantId }), hubs: { list: (tenantId: string) => request("/api/v1/hubs", { tenantId }), get: (tenantId: string, id: string) => request(`/api/v1/hubs/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request("/api/v1/hubs", { tenantId, method: "POST", body: JSON.stringify(body) }), update: (tenantId: string, id: string, body: Record) => request(`/api/v1/hubs/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), dashboard: (tenantId: string, id: string) => request(`/api/v1/hubs/${id}/dashboard`, { tenantId }), }, teams: { list: (tenantId: string, workHubId?: string) => request(`/api/v1/teams${qs({ work_hub_id: workHubId })}`, { tenantId }), get: (tenantId: string, id: string) => request(`/api/v1/teams/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request("/api/v1/teams", { tenantId, method: "POST", body: JSON.stringify(body) }), update: (tenantId: string, id: string, body: Record) => request(`/api/v1/teams/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), }, members: { list: (tenantId: string, teamId?: string) => request(`/api/v1/team-members${qs({ team_id: teamId })}`, { tenantId }), add: (tenantId: string, body: Record) => request("/api/v1/team-members", { tenantId, method: "POST", body: JSON.stringify(body), }), update: (tenantId: string, id: string, body: Record) => request(`/api/v1/team-members/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), }, projects: { list: (tenantId: string, params?: { work_hub_id?: string; team_id?: string }) => request(`/api/v1/projects${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request(`/api/v1/projects/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request("/api/v1/projects", { tenantId, method: "POST", body: JSON.stringify(body) }), update: (tenantId: string, id: string, body: Record) => request(`/api/v1/projects/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), }, tasks: { list: ( tenantId: string, params?: { work_hub_id?: string; project_id?: string; assignee_user_id?: string; status?: string } ) => request(`/api/v1/tasks${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request(`/api/v1/tasks/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request("/api/v1/tasks", { tenantId, method: "POST", body: JSON.stringify(body) }), update: (tenantId: string, id: string, body: Record) => request(`/api/v1/tasks/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), }, contentTypes: { list: (tenantId: string) => request("/api/v1/content-types", { tenantId }), }, content: { list: ( tenantId: string, params?: { work_hub_id?: string; project_id?: string; status?: string; content_type?: string } ) => request(`/api/v1/content${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request(`/api/v1/content/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request("/api/v1/content", { tenantId, method: "POST", body: JSON.stringify(body) }), update: (tenantId: string, id: string, body: Record) => request(`/api/v1/content/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), calendar: ( tenantId: string, params: { work_hub_id: string; team_id?: string; project_id?: string; channel?: string; content_type?: string; status?: string; owner_user_id?: string; } ) => request(`/api/v1/content/calendar${qs(params)}`, { tenantId }), }, approvals: { list: (tenantId: string, params?: { content_id?: string; work_hub_id?: string }) => request(`/api/v1/content/approvals${qs(params)}`, { tenantId }), request: (tenantId: string, body: Record) => request("/api/v1/content/approvals", { tenantId, method: "POST", body: JSON.stringify(body), }), respond: (tenantId: string, id: string, body: Record) => request(`/api/v1/content/approvals/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), }, assets: { list: (tenantId: string, params?: { work_hub_id?: string; content_id?: string }) => request(`/api/v1/assets${qs(params)}`, { tenantId }), create: (tenantId: string, body: Record) => request("/api/v1/assets", { tenantId, method: "POST", body: JSON.stringify(body) }), }, comments: { list: ( tenantId: string, params?: { work_hub_id?: string; subject_type?: string; subject_id?: string } ) => request(`/api/v1/comments${qs(params)}`, { tenantId }), create: (tenantId: string, body: Record) => request("/api/v1/comments", { tenantId, method: "POST", body: JSON.stringify(body) }), }, activity: { list: (tenantId: string, workHubId?: string) => request(`/api/v1/activity${qs({ work_hub_id: workHubId })}`, { tenantId }), }, };