TorbatYar/frontend/modules/workspace/services/workspace-api.ts
Mortezakoohjani a483b47369 feat(workspace): complete production workspace and content operations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-12 11:54:17 +03:30

227 lines
9.2 KiB
TypeScript

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<string, string | undefined | null>) {
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<T>(path: string, options: RequestOptions): Promise<T> {
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<T>;
}
export const workspaceApi = {
health: (): Promise<HealthResponse> => fetch(`${WORKSPACE_BASE}/health`).then((r) => r.json()),
capabilities: (tenantId: string) => request<CapabilitiesResponse>("/capabilities", { tenantId }),
hubs: {
list: (tenantId: string) => request<WorkspaceRecord[]>("/api/v1/hubs", { tenantId }),
get: (tenantId: string, id: string) => request<WorkspaceRecord>(`/api/v1/hubs/${id}`, { tenantId }),
create: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/api/v1/hubs", { tenantId, method: "POST", body: JSON.stringify(body) }),
update: (tenantId: string, id: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>(`/api/v1/hubs/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
dashboard: (tenantId: string, id: string) =>
request<DashboardResponse>(`/api/v1/hubs/${id}/dashboard`, { tenantId }),
},
teams: {
list: (tenantId: string, workHubId?: string) =>
request<WorkspaceRecord[]>(`/api/v1/teams${qs({ work_hub_id: workHubId })}`, { tenantId }),
get: (tenantId: string, id: string) => request<WorkspaceRecord>(`/api/v1/teams/${id}`, { tenantId }),
create: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/api/v1/teams", { tenantId, method: "POST", body: JSON.stringify(body) }),
update: (tenantId: string, id: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>(`/api/v1/teams/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
},
members: {
list: (tenantId: string, teamId?: string) =>
request<WorkspaceRecord[]>(`/api/v1/team-members${qs({ team_id: teamId })}`, { tenantId }),
add: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/api/v1/team-members", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
update: (tenantId: string, id: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>(`/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<WorkspaceRecord[]>(`/api/v1/projects${qs(params)}`, { tenantId }),
get: (tenantId: string, id: string) => request<ProjectDetail>(`/api/v1/projects/${id}`, { tenantId }),
create: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/api/v1/projects", { tenantId, method: "POST", body: JSON.stringify(body) }),
update: (tenantId: string, id: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>(`/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<WorkspaceRecord[]>(`/api/v1/tasks${qs(params)}`, { tenantId }),
get: (tenantId: string, id: string) => request<WorkspaceRecord>(`/api/v1/tasks/${id}`, { tenantId }),
create: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/api/v1/tasks", { tenantId, method: "POST", body: JSON.stringify(body) }),
update: (tenantId: string, id: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>(`/api/v1/tasks/${id}`, {
tenantId,
method: "PATCH",
body: JSON.stringify(body),
}),
},
contentTypes: {
list: (tenantId: string) => request<WorkspaceRecord[]>("/api/v1/content-types", { tenantId }),
},
content: {
list: (
tenantId: string,
params?: { work_hub_id?: string; project_id?: string; status?: string; content_type?: string }
) => request<WorkspaceRecord[]>(`/api/v1/content${qs(params)}`, { tenantId }),
get: (tenantId: string, id: string) => request<ContentDetail>(`/api/v1/content/${id}`, { tenantId }),
create: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/api/v1/content", { tenantId, method: "POST", body: JSON.stringify(body) }),
update: (tenantId: string, id: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>(`/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<WorkspaceRecord[]>(`/api/v1/content/calendar${qs(params)}`, { tenantId }),
},
approvals: {
list: (tenantId: string, params?: { content_id?: string; work_hub_id?: string }) =>
request<WorkspaceRecord[]>(`/api/v1/content/approvals${qs(params)}`, { tenantId }),
request: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/api/v1/content/approvals", {
tenantId,
method: "POST",
body: JSON.stringify(body),
}),
respond: (tenantId: string, id: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>(`/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<WorkspaceRecord[]>(`/api/v1/assets${qs(params)}`, { tenantId }),
create: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/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<WorkspaceRecord[]>(`/api/v1/comments${qs(params)}`, { tenantId }),
create: (tenantId: string, body: Record<string, unknown>) =>
request<WorkspaceRecord>("/api/v1/comments", { tenantId, method: "POST", body: JSON.stringify(body) }),
},
activity: {
list: (tenantId: string, workHubId?: string) =>
request<WorkspaceRecord[]>(`/api/v1/activity${qs({ work_hub_id: workHubId })}`, { tenantId }),
},
};