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 });
},
});
}