import type { CreateDraftResponse, ProjectEditResponse, ProjectIndexPage, ProjectUpdateRequest, TopicEdit, } from "../../contracts/management/contract.ts"; import type { ManagementGateway } from "../../application/ports/management-gateway.ts"; import type { StudioOperationExecutor } from "./http-studio-gateway.ts"; export type { ManagementGateway }; const ROUTE_ID = "TECH_LOG_STUDIO"; /** * 주제·프로젝트 관리 게이트웨이. * *

Studio 게이트웨이와 같은 실패 규약을 쓴다 — 실패는 던지고, 화면은 `usePublicContent` 가 아니라 * Studio 쪽 상태 처리를 그대로 쓴다. 여기서 Result 로 감싸면 이 표면만 다른 규약이 된다. */ export class ManagementGatewayError extends Error { readonly operationId: string; readonly code: string; constructor(operationId: string, code: string) { super(`${operationId}: ${code}`); this.name = "ManagementGatewayError"; this.operationId = operationId; this.code = code; } } export function createHttpManagementGateway( deps: Readonly<{ operations: StudioOperationExecutor }>, ): ManagementGateway { async function run(operationId: string, input: unknown): Promise { const outcome = await deps.operations.execute(operationId, input, { routeId: ROUTE_ID }); if (outcome.kind === "SUCCESS") return outcome.value as T; if (outcome.kind === "PROBLEM") { // The management surface answers with the ADR-006 envelope, which nests // the code under `error` — reading `problem.code` found nothing and every // failure surfaced as the literal "PROBLEM", matching no i18n key. const body = outcome.problem as | Readonly<{ code?: unknown; error?: Readonly<{ code?: unknown }> }> | null; const code = typeof body?.code === "string" ? body.code : typeof body?.error?.code === "string" ? body.error.code : "PROBLEM"; throw new ManagementGatewayError(operationId, code); } throw new ManagementGatewayError(operationId, outcome.kind); } return Object.freeze({ listTopics: () => run("listStudioTopics", {}), createTopic: (input: TopicEdit) => run("createTopic", input), updateTopic: (id: string, body: TopicEdit) => run("updateTopic", { id, body }), deleteTopic: async (id: string, expectedVersion: number) => { await run("deleteTopic", { id, expectedVersion }); }, listProjects: (page?: number, size?: number) => run("listStudioProjects", { ...(page !== undefined ? { page } : {}), ...(size !== undefined ? { size } : {}) }), getProject: (id: string) => run("getProjectForEdit", { id }), createProject: (title: string) => run("createProject", { title }), updateProject: (id: string, body: ProjectUpdateRequest) => run("updateProject", { id, body }), deleteProject: async (id: string, expectedVersion: number) => { await run("deleteProject", { id, expectedVersion }); }, }); }