import type {
CreateDraftResponse,
HomeFocusRequest,
HomeFocusResponse,
ProjectActivityRequest,
ProjectActivityResponse,
UpdateProjectActivityRequest,
ProjectEditResponse,
ProjectIndexPage,
ProjectUpdateRequest,
PublishResponse,
ReleaseEditResponse,
ReleaseIndexPage,
ReleaseUpdateRequest,
TopicEdit,
} from "../../contracts/management/contract.ts";
import type { ManagementGateway } from "../../application/ports/management-gateway.ts";
import { ManagementGatewayError } from "../../application/ports/management-gateway-error.ts";
import type { StudioOperationExecutor } from "./http-studio-gateway.ts";
export type { ManagementGateway };
const ROUTE_ID = "TECH_LOG_STUDIO";
/**
* 주제·프로젝트 관리 게이트웨이.
*
*
Studio 게이트웨이와 같은 실패 규약을 쓴다 — 실패는 던지고, 화면은 `usePublicContent` 가 아니라
* Studio 쪽 상태 처리를 그대로 쓴다. 여기서 Result 로 감싸면 이 표면만 다른 규약이 된다.
*/
export {
ManagementGatewayError,
managementFailureMessage,
} from "../../application/ports/management-gateway-error.ts";
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;
detail?: unknown;
error?: Readonly<{ code?: unknown; message?: unknown }>;
}>
| null;
const code =
typeof body?.code === "string"
? body.code
: typeof body?.error?.code === "string"
? body.error.code
: "PROBLEM";
const detail =
typeof body?.error?.message === "string"
? body.error.message
: typeof body?.detail === "string"
? body.detail
: "";
throw new ManagementGatewayError(operationId, code, detail);
}
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 }),
listReleases: (page?: number, size?: number) =>
run("listStudioReleases", { ...(page !== undefined ? { page } : {}), ...(size !== undefined ? { size } : {}) }),
getRelease: (id: string) => run("getReleaseForEdit", { id }),
createRelease: (title: string) => run("createRelease", { title }),
updateRelease: (id: string, body: ReleaseUpdateRequest) =>
run("updateRelease", { id, body }),
deleteRelease: async (id: string, expectedVersion: number) => {
await run("deleteRelease", { id, expectedVersion });
},
listProjectActivities: (id: string) =>
run("listStudioProjectActivities", { id }),
createProjectActivity: (id: string, body: ProjectActivityRequest) =>
run("createProjectActivity", { id, body }),
updateProjectActivity: (
id: string,
activityId: string,
body: UpdateProjectActivityRequest,
) => run("updateProjectActivity", { id, activityId, body }),
deleteProjectActivity: async (id: string, activityId: string, expectedVersion: number) => {
await run("deleteProjectActivity", { id, activityId, expectedVersion });
},
publishProject: (
id: string,
expectedVersion: number,
visibility: "PUBLIC" | "UNLISTED" = "PUBLIC",
) => run("publishProject", { id, expectedVersion, visibility }),
unpublishProject: (id: string, expectedVersion: number) =>
run("unpublishProject", { id, expectedVersion }),
getHomeFocus: () => run("getHomeFocus", {}),
updateHomeFocus: (body: HomeFocusRequest) =>
run("updateHomeFocus", body),
publishRelease: (id: string, expectedVersion: number) =>
run("publishRelease", { id, expectedVersion }),
archiveRelease: (id: string, expectedVersion: number) =>
run("archiveRelease", { id, expectedVersion }),
deleteDocument: async (
kind: "CASE" | "REFERENCE" | "QUESTION",
id: string,
expectedVersion: number,
) => {
const operationId =
kind === "CASE"
? "deleteCaseDraft"
: kind === "REFERENCE"
? "deleteReferenceDraft"
: "deleteQuestion";
await run(operationId, { id, expectedVersion });
},
deleteDecision: async (projectId: string, decisionId: string, expectedVersion: number) => {
await run("deleteProjectDecision", { id: projectId, decisionId, expectedVersion });
},
deleteProject: async (id: string, expectedVersion: number) => {
await run("deleteProject", { id, expectedVersion });
},
});
}