227 lines
9.2 KiB
TypeScript
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 }),
|
|
},
|
|
};
|