Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148XFUpPfpiuTjxF1EK9c95
109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Api_Fetch.ts
|
|
* 마스터 템플릿 서버 호출 — 계약 `6_계약.md` 서버 길 · 시스템 층은 시스템 관리자만
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
|
|
/** 층 이름 — 서버와 같은 글 */
|
|
export type Layer = "system" | "company" | "personal" | "project";
|
|
/** 서버 종류 — 화면의 「표 양식」 = table · 「도면 양식」 = drawing */
|
|
export type Kind = "table" | "drawing";
|
|
|
|
export interface TemplateInfo {
|
|
종류: Kind;
|
|
이름: string;
|
|
판: string;
|
|
수정일: string;
|
|
}
|
|
|
|
export interface TemplateDoc {
|
|
종류: Kind;
|
|
이름: string;
|
|
/** 프로젝트 층에서 작업본이 아직 없으면(옛 프로젝트) 시스템 양식이 와서 null */
|
|
판: string | null;
|
|
문서: unknown;
|
|
}
|
|
|
|
/** 서버가 판 불일치로 막았을 때 */
|
|
export class StaleError extends Error {}
|
|
|
|
async function call<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const response = await fetch(`${API_BASE_URL}/m02${path}`, {
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json" },
|
|
...init,
|
|
});
|
|
const body = (await response.json().catch(() => ({}))) as { detail?: unknown } & T;
|
|
if (response.status === 409) throw new StaleError(String(body.detail ?? "409"));
|
|
if (!response.ok) {
|
|
const detail = body.detail;
|
|
throw new Error(typeof detail === "string" ? detail : `HTTP ${response.status}`);
|
|
}
|
|
return body;
|
|
}
|
|
|
|
const enc = encodeURIComponent;
|
|
const q = (projectId: string | null): string => (projectId ? `?project_id=${enc(projectId)}` : "");
|
|
const head = (layer: Layer): string =>
|
|
layer === "system" ? "/templates" : `/layers/${layer}/templates`;
|
|
const item = (layer: Layer, kind: Kind, name: string, projectId: string | null): string =>
|
|
`${head(layer)}/${kind}/${enc(name)}${layer === "system" ? "" : q(projectId)}`;
|
|
|
|
/** 시스템 길은 배열 · 층 길은 `{층, 양식: [...]}` — 둘 다 목록으로 */
|
|
export const listTemplates = async (
|
|
layer: Layer,
|
|
projectId: string | null,
|
|
): Promise<TemplateInfo[]> => {
|
|
const got = await call<TemplateInfo[] | { 양식: TemplateInfo[] }>(
|
|
layer === "system" ? head(layer) : `${head(layer)}${q(projectId)}`,
|
|
);
|
|
return Array.isArray(got) ? got : got.양식;
|
|
};
|
|
|
|
export const readTemplate = (
|
|
layer: Layer,
|
|
kind: Kind,
|
|
name: string,
|
|
projectId: string | null,
|
|
): Promise<TemplateDoc> => call(item(layer, kind, name, projectId));
|
|
|
|
export const saveTemplate = (
|
|
layer: Layer,
|
|
kind: Kind,
|
|
name: string,
|
|
projectId: string | null,
|
|
판: string,
|
|
문서: unknown,
|
|
): Promise<TemplateInfo> =>
|
|
call(item(layer, kind, name, projectId), { method: "PUT", body: JSON.stringify({ 판, 문서 }) });
|
|
|
|
/** 시스템 층만 지움 */
|
|
export const deleteTemplate = (kind: Kind, name: string, 판: string): Promise<{ ok: boolean }> =>
|
|
call(`/templates/${kind}/${enc(name)}?판=${enc(판)}`, { method: "DELETE" });
|
|
|
|
/* --- 프로젝트 층 단추 다섯 --- */
|
|
const project = (id: string, tail: string): string => `/projects/${enc(id)}/templates/${tail}`;
|
|
const post = (path: string, body: object = {}): Promise<unknown> =>
|
|
call(path, { method: "POST", body: JSON.stringify(body) });
|
|
|
|
export const resetProject = (id: string): Promise<unknown> => post(project(id, "reset"));
|
|
|
|
export const applyToProject = (
|
|
id: string,
|
|
from: { from: Layer; user_id?: string; project_id?: string; 종류?: Kind; 이름?: string },
|
|
): Promise<unknown> => post(project(id, "apply"), from);
|
|
|
|
export const saveProjectAs = (
|
|
id: string,
|
|
to: "personal" | "company",
|
|
종류: Kind,
|
|
이름: string,
|
|
): Promise<unknown> => post(project(id, "save-as"), { to, 종류, 이름 });
|
|
|
|
export const fetchSources = (id: string): Promise<unknown> => call(`/projects/${enc(id)}/sources`);
|
|
|
|
/** 설계값을 채운 표 문서 — 서버가 그때그때 채움 · 저장 안 함 */
|
|
export const readFilled = (id: string, name: string): Promise<TemplateDoc> =>
|
|
call(`/projects/${enc(id)}/tables/${enc(name)}/filled`);
|