From 11c271313960515443b43e4a3c5496a6387da683 Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Thu, 20 Aug 2026 23:40:15 +0900 Subject: [PATCH] feat: let Studio create the topics and projects publishing requires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publishing needs a topic and nothing could create one. The backend now owns that surface; this is its consumer — the management contract vendored, a gateway over its nine operations, and one Studio screen that lists, creates, and deletes topics and projects. The screen adds no CSS. It reuses the classes the working-copy list already uses, so it inherits Studio's spacing, type, and colour rather than growing a second visual vocabulary beside them. Scope stops at list/create/delete: renaming, phase changes, and visibility are implemented in the backend and declared in the contract, but their screens are a separate design. Two real defects surfaced while making the public port async, and both would have shipped: The search page and the header search dialog shared a query key. With an empty query, `["tech-log","search",""]` was identical for both, so react-query handed one surface the other's cache — different shapes — and the page died reading a field that was not there. Keys now name the surface. The explore filter's selects are uncontrolled and read `defaultValue`, which React applies once. Their options arrive later now, so the first render had nothing to match and the value stayed empty: a topic in the URL no longer showed as selected. The form key includes whether the catalog has arrived, so it remounts with the options present. Controlled inputs would be the other answer, but this form submits to build a URL — the URL owns the value. The route brought its own bookkeeping: a build chunk, a manual accessibility evidence file, and the CI artifact baseline that counts them. The gate pins a digest of its own shape precisely so a new route cannot slip in without that count being reviewed. Test harnesses that render public screens now assemble the query providers and await the settled paint, because the screens they render became async. --- .../a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md | 18 + config/ci/gates.json | 7 + config/runtime/production.json | 2 +- public/release-manifest.json | 13 +- scripts/contracts/ci-gates.ts | 13 +- scripts/generate-tech-log-contract.ts | 8 + scripts/lib/tech-log-serving-contract.ts | 1 + .../installed-contract-contributions.ts | 8 +- .../adapters/create-tech-log-feature-input.ts | 5 + .../adapters/http/http-management-gateway.ts | 64 + .../application/ports/management-gateway.ts | 26 + .../application/tech-log-feature-input.ts | 4 + .../management/canonical-source.json | 87 + .../tech-log/contracts/management/contract.ts | 12 + .../contracts/management/generated.ts | 8387 +++++++++++++++++ .../management/management-api.openapi.yaml | 7065 ++++++++++++++ .../contracts/public/canonical-source.json | 2 +- .../contracts/studio/canonical-source.json | 2 +- ...ch-log-management-contract-contribution.ts | 256 + .../contracts/tech-log-route-contract.ts | 1 + .../public/components/explore-filter-form.tsx | 6 +- .../public/components/search-dialog.tsx | 2 +- .../presentation/public/pages/search-page.tsx | 6 +- .../studio/components/taxonomy-manager.tsx | 276 + .../studio/pages/taxonomy-page.tsx | 5 + .../presentation/studio/studio-provider.tsx | 6 + .../presentation/studio/studio-shell.tsx | 5 + .../presentation/studio/use-studio.ts | 4 + .../presentation/tech-log-route-runtime.tsx | 7 + tests/features/tech-log/asset-picker.test.tsx | 19 +- tests/features/tech-log/feature-input.test.ts | 5 +- .../tech-log/navigation-derivation.test.ts | 1 + .../public-discovery-screens.test.tsx | 66 +- .../tech-log/public-document-screens.test.tsx | 39 +- .../tech-log/public-index-screens.test.tsx | 51 +- tests/features/tech-log/public-shell.test.tsx | 27 +- .../features/tech-log/route-contract.test.ts | 5 +- .../tech-log/runtime-composition.test.ts | 1 + .../studio-decision-authoring.test.tsx | 5 +- .../tech-log/studio-editor-smoke.test.tsx | 4 +- .../tech-log/studio-publication-flow.test.tsx | 4 +- .../tech-log/studio-save-navigation.test.tsx | 13 +- .../tech-log/studio-screens-smoke.test.tsx | 2 + .../tech-log/studio-shell-smoke.test.tsx | 18 +- .../studio-validation-preview.test.tsx | 7 +- .../features/tech-log/style-contract.test.ts | 40 +- .../use-studio-asset-gateway.test.tsx | 2 + tests/helpers/management-gateway-stub.ts | 28 + tests/unit/ci-workflow-generation.test.ts | 4 +- .../unit/task3-selective-integration.test.ts | 2 +- tests/unit/tech-log-serving-contract.test.ts | 1 + vite.config.ts | 1 + 52 files changed, 16509 insertions(+), 134 deletions(-) create mode 100644 artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md create mode 100644 src/features/tech-log/adapters/http/http-management-gateway.ts create mode 100644 src/features/tech-log/application/ports/management-gateway.ts create mode 100644 src/features/tech-log/contracts/management/canonical-source.json create mode 100644 src/features/tech-log/contracts/management/contract.ts create mode 100644 src/features/tech-log/contracts/management/generated.ts create mode 100644 src/features/tech-log/contracts/management/management-api.openapi.yaml create mode 100644 src/features/tech-log/contracts/tech-log-management-contract-contribution.ts create mode 100644 src/features/tech-log/presentation/studio/components/taxonomy-manager.tsx create mode 100644 src/features/tech-log/presentation/studio/pages/taxonomy-page.tsx create mode 100644 tests/helpers/management-gateway-stub.ts diff --git a/artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md b/artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md new file mode 100644 index 0000000..681e27d --- /dev/null +++ b/artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md @@ -0,0 +1,18 @@ +# TECH_LOG_STUDIO_TAXONOMY accessibility review + +Status: pending-manual-review +Route ID: TECH_LOG_STUDIO_TAXONOMY +Release ID: +Reviewer: +Reviewed at: +Signature: +Attestation: pending +M1 Keyboard: pending +M2 Visible focus: pending +M3 Route focus: pending +M4 Modal focus: pending +M5 Error association: pending +M6 Color signal: pending +M7 Reduced motion: pending +Screen reader: pending +Notes: Human review pending; automated evidence does not replace signed keyboard, focus, and screen-reader review. diff --git a/config/ci/gates.json b/config/ci/gates.json index 6bf78f8..b4655a8 100644 --- a/config/ci/gates.json +++ b/config/ci/gates.json @@ -1196,6 +1196,12 @@ "schemaId": "markdown", "production": "source-controlled" }, + { + "id": "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-TAXONOMY-md", + "path": "artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md", + "schemaId": "markdown", + "production": "source-controlled" + }, { "id": "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-NOT-FOUND-md", "path": "artifacts/tests/a11y-manual/TECH_LOG_STUDIO_NOT_FOUND.md", @@ -1949,6 +1955,7 @@ "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-PUBLICATIONS-md", "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-PUBLICATION-PREVIEW-md", "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-ASSETS-md", + "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-TAXONOMY-md", "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-NOT-FOUND-md", "artifact-artifacts-tests-a11y-manual-NOT-FOUND-md", "artifact-artifacts-tests-a11y-manual-report-json" diff --git a/config/runtime/production.json b/config/runtime/production.json index b3f734d..b1d66b4 100644 --- a/config/runtime/production.json +++ b/config/runtime/production.json @@ -15,7 +15,7 @@ "OFFLINE_COMMANDS": "DEFAULT" }, "TECH_LOG_STUDIO_SOURCE": "HTTP", - "TECH_LOG_PUBLIC_SOURCE": "MOCK", + "TECH_LOG_PUBLIC_SOURCE": "HTTP", "FEATURE_OVERRIDES": { "reference-feature": "DEFAULT" } diff --git a/public/release-manifest.json b/public/release-manifest.json index f49e5e8..2ec5e30 100644 --- a/public/release-manifest.json +++ b/public/release-manifest.json @@ -38,21 +38,28 @@ }, "contractSet": { "setAlgorithm": "CA_CONTRACT_SET_V1", - "setDigest": "sha256:c8a29bdf6deadd8951b0a15426fcc119ff85ee9349acc0e8b200c8a5650d8b31", + "setDigest": "sha256:ac1448ced933beece92924be4ffb7beb182c3df26371a83ffeffbb174ce98e72", "packages": [ + { + "packageId": "@tech-log/management-contract", + "version": "1.0.0", + "digest": "sha256:ec5e432215fb041abee980787366a6db29ff1ecdd78416aa9c61e09b9b91022f", + "runtimeProtocolVersion": 1, + "sourceRevision": "6ef5c1c" + }, { "packageId": "@tech-log/public-contract", "version": "2.0.0", "digest": "sha256:8ac71425b38658f34641102b4c2e6e21288c811efebdb92c0a46fb9d4790e23e", "runtimeProtocolVersion": 1, - "sourceRevision": "b98eaf9" + "sourceRevision": "6ef5c1c" }, { "packageId": "@tech-log/studio-contract", "version": "3.0.0", "digest": "sha256:6cae9924403d0761f401643a022980b8e04183eea0d890c143c9fbbbbc7431e4", "runtimeProtocolVersion": 1, - "sourceRevision": "b98eaf9" + "sourceRevision": "6ef5c1c" } ] } diff --git a/scripts/contracts/ci-gates.ts b/scripts/contracts/ci-gates.ts index 2b21f31..2db948e 100644 --- a/scripts/contracts/ci-gates.ts +++ b/scripts/contracts/ci-gates.ts @@ -459,7 +459,12 @@ const CANONICAL_GATE_SHAPE_SHA256 = // Dev release manifest drift fix, item 2: recomputed again after FE-GATE-010 // gained `check-dev-release-manifest`. Same method — 98d19911… was first // reproduced from the previous gates.json before this value was hashed. - "b40962448e617883060e09eb7183837cfea6833519f435f11713efd083210dbe"; + // Taxonomy route: recomputed again after FE-GATE-009 gained the + // TECH_LOG_STUDIO_TAXONOMY manual accessibility evidence artifact. The gate + // lists one evidence artifact per installed route and refuses a set that does + // not match the route scope exactly, so adding a route necessarily moves this + // digest — that is the point of pinning it. + "8c73d4477392a63e91088ff91e53293e7fc3ace891f8eb3da3b0d05fa714df60"; function canonicalGateShapeSha256(gates: CiGateContract["gates"]): string { const normalized = gates.map( @@ -512,8 +517,10 @@ function canonicalAuthorityBaselineFailures(contract: CiGateContract): string[] // Template merge. 126 product artifacts plus the two the template added. // Task 11 added one more: the TECH_LOG_STUDIO_ASSETS manual a11y evidence file. // Alignment follow-up, item 2 added the TechLog junit report. - if (contract.artifacts.length !== 130) { - failures.push(`artifact authority baseline must contain exactly 130 artifacts; received ${contract.artifacts.length}`); + // The taxonomy route added its own manual a11y evidence file — every installed + // route carries one, and the gate checks that the two sets match exactly. + if (contract.artifacts.length !== 131) { + failures.push(`artifact authority baseline must contain exactly 131 artifacts; received ${contract.artifacts.length}`); } if (contract.stages.length !== 5) { failures.push(`stage authority baseline must contain exactly 5 stages; received ${contract.stages.length}`); diff --git a/scripts/generate-tech-log-contract.ts b/scripts/generate-tech-log-contract.ts index 02ce433..30070c6 100644 --- a/scripts/generate-tech-log-contract.ts +++ b/scripts/generate-tech-log-contract.ts @@ -50,6 +50,14 @@ const CONTRACTS: readonly ContractTarget[] = Object.freeze([ generated: "src/features/tech-log/contracts/public/generated.ts", sourceRecord: "src/features/tech-log/contracts/public/canonical-source.json", }), + Object.freeze({ + name: "management", + packageId: "@tech-log/management-contract", + canonicalYaml: `${CANONICAL_ROOT}/contracts/openapi/studio-management-v1.yaml`, + vendorYaml: "src/features/tech-log/contracts/management/management-api.openapi.yaml", + generated: "src/features/tech-log/contracts/management/generated.ts", + sourceRecord: "src/features/tech-log/contracts/management/canonical-source.json", + }), ]); const OPENAPI_TYPESCRIPT = "openapi-typescript@7.9.1"; diff --git a/scripts/lib/tech-log-serving-contract.ts b/scripts/lib/tech-log-serving-contract.ts index 9c5c506..caa7bf8 100644 --- a/scripts/lib/tech-log-serving-contract.ts +++ b/scripts/lib/tech-log-serving-contract.ts @@ -56,6 +56,7 @@ const studioSpaPathPatterns = Object.freeze([ // reload of /studio/assets was served the in-shell Studio 404 -- the screen // was only reachable by client-side navigation from another Studio page. "^/studio/assets$", + "^/studio/taxonomy$", "^/studio/documents$", "^/studio/documents/new$", "^/studio/documents/[^/]+/(edit|validation|preview|publish)$", diff --git a/src/features/installed-contract-contributions.ts b/src/features/installed-contract-contributions.ts index 448d646..753abd9 100644 --- a/src/features/installed-contract-contributions.ts +++ b/src/features/installed-contract-contributions.ts @@ -6,6 +6,7 @@ import { import { REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION } from "./reference-feature/contracts/reference-feature-contract-contribution.ts"; import { REFERENCE_FEATURE_ID } from "./reference-feature/contracts/reference-feature-contract.ts"; import { INSTALLED_PRODUCT_FEATURE_IDS } from "./installed-product-manifest.ts"; +import { TECH_LOG_MANAGEMENT_CONTRIBUTION } from "./tech-log/contracts/tech-log-management-contract-contribution.ts"; import { TECH_LOG_PUBLIC_CONTRIBUTION } from "./tech-log/contracts/tech-log-public-contract-contribution.ts"; import { TECH_LOG_STUDIO_CONTRIBUTION } from "./tech-log/contracts/tech-log-studio-contract-contribution.ts"; @@ -23,8 +24,13 @@ export const INSTALLED_CONTRACT_CONTRIBUTIONS: readonly InstalledContractContrib REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION, TECH_LOG_STUDIO_CONTRIBUTION, TECH_LOG_PUBLIC_CONTRIBUTION, + TECH_LOG_MANAGEMENT_CONTRIBUTION, ] - : [TECH_LOG_STUDIO_CONTRIBUTION, TECH_LOG_PUBLIC_CONTRIBUTION], + : [ + TECH_LOG_STUDIO_CONTRIBUTION, + TECH_LOG_PUBLIC_CONTRIBUTION, + TECH_LOG_MANAGEMENT_CONTRIBUTION, + ], ); export const COMPOSED_CONTRACT_CONTRIBUTIONS = composeContractContributions( diff --git a/src/features/tech-log/adapters/create-tech-log-feature-input.ts b/src/features/tech-log/adapters/create-tech-log-feature-input.ts index 416506f..194670e 100644 --- a/src/features/tech-log/adapters/create-tech-log-feature-input.ts +++ b/src/features/tech-log/adapters/create-tech-log-feature-input.ts @@ -12,6 +12,7 @@ import { import type { CsrfTokenProvider } from "./http/studio-session-csrf.ts"; import { createMockStudioAssetGateway } from "./mock/mock-studio-asset-gateway.ts"; import { createMockStudioGateway } from "./mock/mock-studio-gateway.ts"; +import { createHttpManagementGateway } from "./http/http-management-gateway.ts"; import { createHttpPublicContentGateway } from "./http/http-public-content-gateway.ts"; import { publicContentQueries } from "./static/public-query.ts"; @@ -76,8 +77,12 @@ export function createTechLogFeatureInstalledInput( ? publicContentQueries : createHttpPublicContentGateway({ operations: context.contractOperations }); + const createManagementGateway = () => + createHttpManagementGateway({ operations: context.contractOperations }); + const input: TechLogFeatureInput = Object.freeze({ publicContent, + createManagementGateway, createStudioGateway, createStudioAssetGateway, }); diff --git a/src/features/tech-log/adapters/http/http-management-gateway.ts b/src/features/tech-log/adapters/http/http-management-gateway.ts new file mode 100644 index 0000000..9f62f14 --- /dev/null +++ b/src/features/tech-log/adapters/http/http-management-gateway.ts @@ -0,0 +1,64 @@ +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") { + const problem = outcome.problem as Readonly<{ code?: string }> | null; + throw new ManagementGatewayError(operationId, problem?.code ?? "PROBLEM"); + } + 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 }); + }, + }); +} diff --git a/src/features/tech-log/application/ports/management-gateway.ts b/src/features/tech-log/application/ports/management-gateway.ts new file mode 100644 index 0000000..e0a7057 --- /dev/null +++ b/src/features/tech-log/application/ports/management-gateway.ts @@ -0,0 +1,26 @@ +import type { + CreateDraftResponse, + ProjectEditResponse, + ProjectIndexPage, + ProjectUpdateRequest, + TopicEdit, +} from "../../contracts/management/contract.ts"; + +/** + * 주제·프로젝트 관리 표면. + * + *

Studio 게이트웨이와 같은 실패 규약이다 — 실패는 던지고, 화면이 잡는다. MOCK 대응물을 두지 + * 않는 것도 의도다: 이 표면은 백엔드가 없으면 존재할 이유가 없고, 픽스처를 만들면 실제로는 만들 + * 수 없는 주제를 화면이 보여주게 된다. + */ +export type ManagementGateway = Readonly<{ + listTopics(): Promise; + createTopic(input: TopicEdit): Promise; + updateTopic(id: string, body: TopicEdit): Promise; + deleteTopic(id: string, expectedVersion: number): Promise; + listProjects(page?: number, size?: number): Promise; + getProject(id: string): Promise; + createProject(title: string): Promise; + updateProject(id: string, body: ProjectUpdateRequest): Promise; + deleteProject(id: string, expectedVersion: number): Promise; +}>; diff --git a/src/features/tech-log/application/tech-log-feature-input.ts b/src/features/tech-log/application/tech-log-feature-input.ts index 8f150f1..fadb72c 100644 --- a/src/features/tech-log/application/tech-log-feature-input.ts +++ b/src/features/tech-log/application/tech-log-feature-input.ts @@ -1,5 +1,6 @@ import type { PublicContentQueries } from "./ports/public-content-queries.ts"; import type { StudioAssetGateway } from "./ports/studio-asset-gateway.ts"; +import type { ManagementGateway } from "./ports/management-gateway.ts"; import type { StudioGateway } from "./ports/studio-gateway.ts"; export const TECH_LOG_FEATURE_ID = "tech-log" as const; @@ -8,6 +9,9 @@ export type TechLogFeatureInput = Readonly<{ publicContent: PublicContentQueries; createStudioGateway(): StudioGateway; createStudioAssetGateway(): StudioAssetGateway; + // 주제·프로젝트 관리. MOCK 대응물이 없다 — 이 표면은 백엔드가 없으면 존재할 이유가 없고, + // 픽스처를 만들면 실제로는 못 만드는 주제를 화면이 보여주게 된다. + createManagementGateway(): ManagementGateway; }>; declare module "../../../application/ports/in/application-api.ts" { diff --git a/src/features/tech-log/contracts/management/canonical-source.json b/src/features/tech-log/contracts/management/canonical-source.json new file mode 100644 index 0000000..bf05689 --- /dev/null +++ b/src/features/tech-log/contracts/management/canonical-source.json @@ -0,0 +1,87 @@ +{ + "packageId": "@tech-log/management-contract", + "version": "1.0.0", + "digest": "sha256:ec5e432215fb041abee980787366a6db29ff1ecdd78416aa9c61e09b9b91022f", + "sourceRevision": "6ef5c1c", + "operationIds": [ + "createCaseDraft", + "getCaseForEdit", + "updateCaseDraft", + "deleteCaseDraft", + "createReferenceDraft", + "getReferenceForEdit", + "updateReferenceDraft", + "deleteReferenceDraft", + "validateCase", + "submitReviewCase", + "returnToDraftCase", + "unpublishCase", + "archiveCase", + "restoreCase", + "publishCase", + "validateReference", + "submitReviewReference", + "returnToDraftReference", + "unpublishReference", + "archiveReference", + "restoreReference", + "publishReference", + "createQuestion", + "listStudioQuestions", + "getQuestionForEdit", + "updateQuestion", + "deleteQuestion", + "addQuestionUpdate", + "updateQuestionUpdate", + "deleteQuestionUpdate", + "resolveQuestion", + "startQuestionInvestigation", + "pauseQuestion", + "resumeQuestion", + "reopenQuestion", + "archiveQuestion", + "publishQuestion", + "unpublishQuestion", + "createProject", + "listStudioProjects", + "getProjectForEdit", + "updateProject", + "deleteProject", + "changeProjectPhase", + "publishProject", + "unpublishProject", + "createProjectDecision", + "listStudioProjectDecisions", + "getProjectDecision", + "updateProjectDecision", + "acceptProjectDecision", + "rejectProjectDecision", + "supersedeProjectDecision", + "createRelease", + "listStudioReleases", + "getReleaseForEdit", + "updateRelease", + "deleteRelease", + "publishRelease", + "archiveRelease", + "listStudioTopics", + "createTopic", + "updateTopic", + "deleteTopic", + "listStudioTags", + "createTag", + "updateTag", + "deleteTag", + "getStudioSite", + "updateStudioSite", + "getStudioProfile", + "updateStudioProfile", + "publishProfile", + "unpublishProfile", + "getHomeFocus", + "updateHomeFocus", + "listStudioProjectActivities", + "createProjectActivity", + "updateProjectActivity" + ] +} diff --git a/src/features/tech-log/contracts/management/contract.ts b/src/features/tech-log/contracts/management/contract.ts new file mode 100644 index 0000000..4984668 --- /dev/null +++ b/src/features/tech-log/contracts/management/contract.ts @@ -0,0 +1,12 @@ +import type { components } from "./generated.ts"; + +type Schemas = components["schemas"]; + +export type TopicEdit = Schemas["TopicEdit"]; +export type ProjectEditResponse = Schemas["ProjectEditResponse"]; +export type ProjectIndexItem = Schemas["ProjectIndexItem"]; +export type ProjectIndexPage = Schemas["ProjectIndexPage"]; +export type ProjectUpdateRequest = Schemas["ProjectUpdateRequest"]; +export type CreateDraftRequest = Schemas["CreateDraftRequest"]; +export type CreateDraftResponse = Schemas["CreateDraftResponse"]; +export type ExpectedVersionRequest = Schemas["ExpectedVersionRequest"]; diff --git a/src/features/tech-log/contracts/management/generated.ts b/src/features/tech-log/contracts/management/generated.ts new file mode 100644 index 0000000..a078080 --- /dev/null +++ b/src/features/tech-log/contracts/management/generated.ts @@ -0,0 +1,8387 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/v1/studio/cases": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["createCaseDraft"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getCaseForEdit"]; + put: operations["updateCaseDraft"]; + post?: never; + delete: operations["deleteCaseDraft"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["createReferenceDraft"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getReferenceForEdit"]; + put: operations["updateReferenceDraft"]; + post?: never; + delete: operations["deleteReferenceDraft"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["validateCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/submit-review": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["submitReviewCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/return-to-draft": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["returnToDraftCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/restore": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["restoreCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["validateReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/submit-review": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["submitReviewReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/return-to-draft": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["returnToDraftReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/restore": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["restoreReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioQuestions"]; + put?: never; + post: operations["createQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getQuestionForEdit"]; + put: operations["updateQuestion"]; + post?: never; + delete: operations["deleteQuestion"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/updates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["addQuestionUpdate"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/updates/{updateId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateQuestionUpdate"]; + post?: never; + delete: operations["deleteQuestionUpdate"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/resolve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["resolveQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/start-investigation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["startQuestionInvestigation"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/pause": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["pauseQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/resume": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["resumeQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/reopen": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["reopenQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioProjects"]; + put?: never; + post: operations["createProject"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getProjectForEdit"]; + put: operations["updateProject"]; + post?: never; + delete: operations["deleteProject"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/change-phase": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["changeProjectPhase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishProject"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishProject"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioProjectDecisions"]; + put?: never; + post: operations["createProjectDecision"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions/{decisionId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getProjectDecision"]; + put: operations["updateProjectDecision"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions/{decisionId}/accept": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["acceptProjectDecision"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions/{decisionId}/reject": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["rejectProjectDecision"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions/{decisionId}/supersede": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["supersedeProjectDecision"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/releases": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioReleases"]; + put?: never; + post: operations["createRelease"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/releases/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getReleaseForEdit"]; + put: operations["updateRelease"]; + post?: never; + delete: operations["deleteRelease"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/releases/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishRelease"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/releases/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveRelease"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/topics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioTopics"]; + put?: never; + post: operations["createTopic"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/topics/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateTopic"]; + post?: never; + delete: operations["deleteTopic"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/tags": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioTags"]; + put?: never; + post: operations["createTag"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/tags/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateTag"]; + post?: never; + delete: operations["deleteTag"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/site": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getStudioSite"]; + put: operations["updateStudioSite"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/profile": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getStudioProfile"]; + put: operations["updateStudioProfile"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/profile/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishProfile"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/profile/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishProfile"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/home-focus": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getHomeFocus"]; + put: operations["updateHomeFocus"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/activities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioProjectActivities"]; + put?: never; + post: operations["createProjectActivity"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/activities/{activityId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateProjectActivity"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + ResponseMeta: { + requestId: string; + traceId: string; + correlationId?: string | null; + /** @description 백엔드 템플릿의 ResponseMeta record 가 직렬화하는 자리다. 페이지 정보는 payload 의 `page` 가 소유하므로 이 관리 표면에서는 항상 null 이다. */ + page?: { + [key: string]: unknown; + } | null; + }; + ApiError: { + /** + * @description `INTERNAL_ERROR` 는 이 기능이 아니라 스켈레톤의 공통 처리기가 내는 코드다. 계약이 그것까지 열거해야 500 응답이 계약을 벗어나지 않는다. + * @enum {string} + */ + code: "AUTHENTICATION_REQUIRED" | "STUDIO_ACCESS_DENIED" | "REQUEST_VALIDATION_FAILED" | "VERSION_CONFLICT" | "TOPIC_NOT_FOUND" | "TOPIC_NAME_TAKEN" | "TOPIC_SLUG_TAKEN" | "TOPIC_IN_USE" | "PROJECT_NOT_FOUND" | "PROJECT_SLUG_TAKEN" | "PROJECT_IN_USE" | "INTERNAL_ERROR"; + /** @enum {string} */ + category: "VALIDATION" | "AUTH" | "AUTHZ" | "NOT_FOUND" | "CONFLICT" | "RATE_LIMIT" | "TRANSIENT_DEPENDENCY" | "PERMANENT_DEPENDENCY" | "DATA_INTEGRITY" | "INTERNAL"; + message: string; + retryable: boolean; + details?: components["schemas"]["ValidationErrorDetails"] | components["schemas"]["VersionConflictDetails"] | null; + }; + ValidationErrorDetails: { + fieldErrors: components["schemas"]["FieldError"][]; + }; + VersionConflictDetails: { + /** Format: int64 */ + currentVersion: number; + }; + ErrorEnvelope: { + /** @constant */ + success: false; + error: components["schemas"]["ApiError"]; + meta: components["schemas"]["ResponseMeta"]; + }; + TopicEditEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["TopicEdit"]; + meta: components["schemas"]["ResponseMeta"]; + }; + TopicEditListEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["TopicEdit"][]; + meta: components["schemas"]["ResponseMeta"]; + }; + CreateDraftResponseEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["CreateDraftResponse"]; + meta: components["schemas"]["ResponseMeta"]; + }; + ProjectIndexPageEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["ProjectIndexPage"]; + meta: components["schemas"]["ResponseMeta"]; + }; + ProjectEditResponseEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["ProjectEditResponse"]; + meta: components["schemas"]["ResponseMeta"]; + }; + FieldError: { + field: string; + code: string; + message: string; + }; + ProblemDetails: { + type: string; + title: string; + status: number; + code: string; + detail: string; + instance: string; + traceId: string; + fieldErrors?: components["schemas"]["FieldError"][]; + /** Format: int64 */ + currentVersion?: number; + }; + PageMetadata: { + number: number; + size: number; + /** Format: int64 */ + totalElements: number; + totalPages: number; + hasPrevious: boolean; + hasNext: boolean; + }; + TagSummary: { + name: string; + slug: string; + }; + ProjectSummary: { + name: string; + slug: string; + path: string; + }; + AssetReference: { + /** Format: uuid */ + assetId: string; + url: string; + altText?: string; + width?: number; + height?: number; + contentType?: string; + }; + RelatedEntry: { + /** @enum {string} */ + type: "CASE" | "REFERENCE" | "QUESTION" | "PROJECT" | "PROJECT_DECISION" | "RELEASE"; + title: string; + summary?: string; + path: string; + }; + PublicationStatus: { + /** @enum {string} */ + state: "NEVER_PUBLISHED" | "ACTIVE" | "WITHDRAWN"; + /** Format: int64 */ + publishedSourceVersion?: number; + hasUnpublishedChanges: boolean; + canonicalPath?: string; + /** Format: date-time */ + publishedAt?: string; + }; + ValidationIssue: { + /** @enum {string} */ + severity: "ERROR" | "WARNING"; + code: string; + field?: string; + message: string; + }; + ValidationResponse: { + validForSave: boolean; + validForPublish: boolean; + issues: components["schemas"]["ValidationIssue"][]; + }; + CreateDraftRequest: { + title: string; + }; + CreateDraftResponse: { + /** Format: uuid */ + id: string; + /** @enum {string} */ + status: "DRAFT"; + /** Format: int64 */ + version: number; + /** Format: date-time */ + createdAt: string; + }; + RelationEdit: { + /** Format: uuid */ + primaryProjectId?: string; + relatedProjectIds?: string[]; + /** Format: uuid */ + originQuestionId?: string; + relatedDocumentIds?: string[]; + derivedReferenceIds?: string[]; + }; + CaseUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + title: string; + slug?: string; + problemSummary: string; + conclusionSummary: string; + environmentSummary?: string[]; + content: string; + contentFormatVersion: number; + /** Format: uuid */ + primaryTopicId?: string; + tagIds: string[]; + relations: components["schemas"]["RelationEdit"]; + /** Format: uuid */ + coverAssetId?: string; + /** Format: date-time */ + lastVerifiedAt?: string; + /** @enum {string} */ + targetVisibility?: "PRIVATE" | "UNLISTED" | "PUBLIC"; + }; + CaseEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + title: string; + slug?: string; + problemSummary: string; + conclusionSummary: string; + environmentSummary?: string[]; + content: string; + contentFormatVersion: number; + /** Format: uuid */ + primaryTopicId?: string; + tagIds: string[]; + relations: components["schemas"]["RelationEdit"]; + /** Format: uuid */ + coverAssetId?: string; + /** Format: date-time */ + lastVerifiedAt?: string; + workflowStatus: string; + targetVisibility: string; + publication: components["schemas"]["PublicationStatus"]; + /** Format: date-time */ + updatedAt: string; + }; + ReferenceUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + title: string; + slug?: string; + scopeSummary: string; + appliesTo: string[]; + excludedScope: string[]; + /** @enum {string} */ + freshnessStatus: "CURRENT" | "REVIEW_DUE" | "HISTORICAL"; + content: string; + contentFormatVersion: number; + /** Format: uuid */ + primaryTopicId?: string; + tagIds: string[]; + relations: components["schemas"]["RelationEdit"]; + /** Format: uuid */ + coverAssetId?: string; + /** Format: date-time */ + lastVerifiedAt?: string; + /** @enum {string} */ + targetVisibility?: "PRIVATE" | "UNLISTED" | "PUBLIC"; + }; + ReferenceEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + title: string; + slug?: string; + scopeSummary: string; + appliesTo: string[]; + excludedScope: string[]; + freshnessStatus: string; + content: string; + contentFormatVersion: number; + /** Format: uuid */ + primaryTopicId?: string; + tagIds: string[]; + relations: components["schemas"]["RelationEdit"]; + /** Format: uuid */ + coverAssetId?: string; + /** Format: date-time */ + lastVerifiedAt?: string; + workflowStatus: string; + targetVisibility: string; + publication: components["schemas"]["PublicationStatus"]; + /** Format: date-time */ + updatedAt: string; + }; + ExpectedVersionRequest: { + /** Format: int64 */ + expectedVersion: number; + }; + PublishRequest: { + /** Format: int64 */ + expectedVersion: number; + /** @enum {string} */ + visibility: "PUBLIC" | "UNLISTED"; + }; + PublishResponse: { + /** Format: uuid */ + id: string; + /** @enum {string} */ + status: "PUBLISHED"; + /** @enum {string} */ + visibility: "PUBLIC" | "UNLISTED"; + canonicalPath: string; + /** Format: date-time */ + publishedAt: string; + /** Format: int64 */ + version: number; + }; + QuestionPointEdit: { + /** Format: uuid */ + id?: string; + /** @enum {string} */ + kind: "FACT" | "ASSUMPTION" | "UNKNOWN" | "CONSTRAINT"; + content: string; + displayOrder: number; + }; + QuestionUpdateRequest: { + /** @enum {string} */ + type: "OBSERVATION" | "EVIDENCE" | "SCOPE_CHANGE" | "BLOCKER" | "NEXT_STEP" | "RESOLUTION" | "RESOLUTION_REOPENED"; + title: string; + bodyMarkdown: string; + /** @enum {string} */ + visibility: "PRIVATE" | "PUBLIC"; + /** Format: date-time */ + occurredAt: string; + /** Format: int64 */ + expectedVersion: number; + }; + QuestionUpdateResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + questionVersion: number; + type: string; + title: string; + bodyMarkdown: string; + visibility: string; + /** Format: date-time */ + occurredAt: string; + }; + QuestionUpdateBody: { + /** Format: int64 */ + expectedVersion: number; + question: string; + slug?: string; + summary: string; + context: string; + importance: string; + nextVerification?: string; + /** @enum {string} */ + targetVisibility?: "PRIVATE" | "UNLISTED" | "PUBLIC"; + /** Format: uuid */ + primaryProjectId?: string; + /** Format: uuid */ + topicId?: string; + tagIds: string[]; + points: components["schemas"]["QuestionPointEdit"][]; + }; + QuestionEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + question: string; + slug?: string; + summary?: string; + context?: string; + importance?: string; + nextVerification?: string; + status: string; + targetVisibility: string; + /** Format: uuid */ + primaryProjectId?: string; + /** Format: uuid */ + topicId?: string; + tagIds?: string[]; + points: components["schemas"]["QuestionPointEdit"][]; + updates: components["schemas"]["QuestionUpdateResponse"][]; + resolution?: { + type?: string; + summary?: string; + /** Format: date-time */ + resolvedAt?: string; + }; + publication: components["schemas"]["PublicationStatus"]; + /** Format: date-time */ + updatedAt: string; + }; + ResolveQuestionRequest: { + /** Format: int64 */ + expectedVersion: number; + /** @enum {string} */ + resolutionType: "DECISION_MADE" | "ASSUMPTION_REJECTED" | "QUESTION_REFRAMED" | "NO_LONGER_RELEVANT"; + resolutionSummary: string; + /** Format: date-time */ + resolvedAt: string; + createCaseDraft: boolean; + createProjectDecision: boolean; + }; + ResolveQuestionResponse: { + /** Format: uuid */ + questionId: string; + /** @enum {string} */ + status: "RESOLVED"; + /** Format: int64 */ + version: number; + /** Format: uuid */ + createdCaseDraftId?: string; + /** Format: uuid */ + createdProjectDecisionId?: string; + }; + ProjectUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + name: string; + slug?: string; + oneLinePurpose: string; + purposeMarkdown: string; + boundaryMarkdown: string; + systemOverviewMarkdown?: string; + phase: string; + currentObjective?: string; + nextStep?: string; + technologyLabels: string[]; + /** @enum {string} */ + targetVisibility: "PRIVATE" | "UNLISTED" | "PUBLIC"; + featuredOrder?: number; + topicIds?: string[]; + documentLinks?: components["schemas"]["ProjectResourceLinkEdit"][]; + questionLinks?: components["schemas"]["ProjectResourceLinkEdit"][]; + }; + ProjectEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + name: string; + slug?: string; + oneLinePurpose?: string; + purposeMarkdown?: string; + boundaryMarkdown?: string; + systemOverviewMarkdown?: string; + phase: string; + currentObjective?: string; + nextStep?: string; + technologyLabels?: string[]; + workflowStatus: string; + targetVisibility: string; + featuredOrder?: number; + publication: components["schemas"]["PublicationStatus"]; + /** Format: date-time */ + updatedAt: string; + topicIds?: string[]; + documentLinks?: components["schemas"]["ProjectResourceLinkEdit"][]; + questionLinks?: components["schemas"]["ProjectResourceLinkEdit"][]; + }; + ChangeProjectPhaseRequest: { + /** Format: int64 */ + expectedVersion: number; + phase: string; + reason: string; + publishActivity: boolean; + }; + DecisionUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + statement: string; + rationaleMarkdown: string; + consequences: string[]; + alternativesMarkdown: string; + /** @enum {string} */ + targetVisibility: "PRIVATE" | "PUBLIC"; + /** Format: uuid */ + sourceQuestionId?: string; + /** Format: uuid */ + sourceCaseId?: string; + isFeatured?: boolean; + /** Format: date-time */ + decidedAt?: string; + }; + DecisionResponse: components["schemas"]["DecisionUpdateRequest"] & { + /** Format: uuid */ + id: string; + /** Format: uuid */ + projectId: string; + status: string; + /** Format: int64 */ + version: number; + /** Format: uuid */ + supersededById?: string; + }; + ReleaseUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + versionLabel: string; + title: string; + summary: string; + /** Format: date */ + releasedOn?: string; + changeTypes: string[]; + reasonMarkdown?: string; + changesMarkdown: string; + userImpactMarkdown?: string; + implementationImpactMarkdown?: string; + verificationMarkdown: string; + knownLimitationsMarkdown?: string; + relatedResources?: { + resourceType: string; + /** Format: uuid */ + resourceId: string; + }[]; + }; + ReleaseEditResponse: components["schemas"]["ReleaseUpdateRequest"] & { + /** Format: uuid */ + id: string; + workflowStatus: string; + /** Format: int64 */ + version: number; + publication: components["schemas"]["PublicationStatus"]; + }; + TopicEdit: { + /** + * Format: uuid + * @description 응답에만 실린다. 경로가 `/topics/{id}`이므로 이 값이 없으면 목록에서 어떤 주제도 + * 주소지정할 수 없다 — ProjectEditResponse / CaseEditResponse는 같은 이유로 이미 + * id를 싣고 있었고, TopicEdit만 빠져 있었다. 쓰기 요청에 와도 서버는 무시한다; + * 수정 대상은 경로가 소유한다. + */ + id?: string; + /** + * Format: int64 + * @description 응답에만 실린다. 다음 수정에서 보낼 `expectedVersion`의 출처다. 이것이 없으면 + * 클라이언트가 낙관적 잠금에 쓸 값을 얻을 방법이 없다. + */ + version?: number; + name: string; + slug: string; + description?: string; + scope?: string; + /** @enum {string} */ + status?: "ACTIVE" | "ARCHIVED"; + /** Format: int64 */ + expectedVersion?: number; + /** Format: uuid */ + featuredReferenceId?: string; + featuredCaseIds?: string[]; + }; + TagEdit: { + name: string; + slug: string; + /** Format: int64 */ + expectedVersion?: number; + }; + SiteConfigRequest: { + /** Format: int64 */ + expectedVersion: number; + brandTitle: string; + identityStatement: string; + operatorDisplayName: string; + shortIdentity?: string; + /** Format: uuid */ + avatarAssetId?: string; + contacts: Record[]; + }; + ProfileEditRequest: { + /** Format: int64 */ + expectedVersion: number; + headline: string; + introductionMarkdown?: string; + workingModel: Record[]; + territories: Record[]; + selectedEvidence: Record[]; + trajectory: Record[]; + contacts: Record[]; + /** @enum {string} */ + targetVisibility: "PRIVATE" | "PUBLIC"; + }; + HomeFocusRequest: { + /** Format: int64 */ + expectedVersion: number; + /** @enum {string} */ + defaultType?: "CURRENT_WORK" | "OPEN_QUESTION" | "RECENT_DECISION"; + /** Format: uuid */ + currentProjectId?: string; + /** Format: uuid */ + openQuestionId?: string; + /** Format: uuid */ + recentDecisionId?: string; + }; + QuestionIndexItem: { + /** Format: uuid */ + id: string; + question: string; + status: string; + targetVisibility: string; + primaryProject?: components["schemas"]["ProjectSummary"]; + nextVerification?: string; + /** Format: date-time */ + updatedAt: string; + /** Format: int64 */ + version: number; + publication: components["schemas"]["PublicationStatus"]; + }; + QuestionIndexPage: { + items: components["schemas"]["QuestionIndexItem"][]; + page: components["schemas"]["PageMetadata"]; + }; + ProjectIndexItem: { + /** Format: uuid */ + id: string; + name: string; + phase: string; + workflowStatus: string; + targetVisibility: string; + currentObjective?: string; + nextStep?: string; + /** Format: date-time */ + updatedAt: string; + /** Format: int64 */ + version: number; + publication: components["schemas"]["PublicationStatus"]; + }; + ProjectIndexPage: { + items: components["schemas"]["ProjectIndexItem"][]; + page: components["schemas"]["PageMetadata"]; + }; + ReleaseIndexItem: { + /** Format: uuid */ + id: string; + versionLabel: string; + title: string; + /** Format: date */ + releasedOn?: string; + workflowStatus: string; + /** Format: date-time */ + updatedAt: string; + /** Format: int64 */ + version: number; + publication: components["schemas"]["PublicationStatus"]; + }; + ReleaseIndexPage: { + items: components["schemas"]["ReleaseIndexItem"][]; + page: components["schemas"]["PageMetadata"]; + }; + SiteConfigResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + brandTitle: string; + identityStatement: string; + operatorDisplayName: string; + shortIdentity?: string; + /** Format: uuid */ + avatarAssetId?: string; + contacts: Record[]; + }; + ProfileEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + headline: string; + introductionMarkdown?: string; + workingModel: Record[]; + territories: Record[]; + selectedEvidence: Record[]; + trajectory: Record[]; + contacts: Record[]; + targetVisibility: string; + publication: components["schemas"]["PublicationStatus"]; + }; + HomeFocusResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + defaultType?: string; + /** Format: uuid */ + currentProjectId?: string; + /** Format: uuid */ + openQuestionId?: string; + /** Format: uuid */ + recentDecisionId?: string; + }; + ProjectResourceLinkEdit: { + /** Format: uuid */ + resourceId: string; + /** @enum {string} */ + relationType: "PRIMARY" | "RELATED"; + featuredOrder?: number; + }; + CreateDecisionRequest: { + /** Format: int64 */ + expectedProjectVersion: number; + statement: string; + rationaleMarkdown: string; + consequences: string[]; + alternativesMarkdown: string; + /** @enum {string} */ + targetVisibility: "PRIVATE" | "PUBLIC"; + /** Format: uuid */ + sourceQuestionId?: string; + /** Format: uuid */ + sourceCaseId?: string; + isFeatured?: boolean; + /** Format: date-time */ + decidedAt?: string; + }; + SupersedeDecisionRequest: { + /** Format: int64 */ + expectedVersion: number; + /** Format: uuid */ + supersededById: string; + }; + ProjectActivityRequest: { + /** Format: int64 */ + expectedProjectVersion: number; + activityType: string; + title: string; + summary?: string; + /** @enum {string} */ + visibility: "PRIVATE" | "PUBLIC"; + relatedResourceType?: string; + /** Format: uuid */ + relatedResourceId?: string; + /** Format: date-time */ + occurredAt: string; + }; + ProjectActivityResponse: { + /** Format: uuid */ + id: string; + /** Format: uuid */ + projectId: string; + activityType: string; + title: string; + summary?: string; + visibility: string; + origin: string; + relatedResourceType?: string; + /** Format: uuid */ + relatedResourceId?: string; + /** Format: date-time */ + occurredAt: string; + /** Format: int64 */ + version: number; + }; + UpdateProjectActivityRequest: { + /** Format: int64 */ + expectedVersion: number; + title: string; + summary?: string; + /** @enum {string} */ + visibility: "PRIVATE" | "PUBLIC"; + /** Format: date-time */ + occurredAt: string; + }; + }; + responses: never; + parameters: { + CsrfToken: string; + }; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + createCaseDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getCaseForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateCaseDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaseUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteCaseDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createReferenceDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getReferenceForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateReferenceDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ReferenceUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteReferenceDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + validateCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ValidationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + submitReviewCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + returnToDraftCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + archiveCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + restoreCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PublishRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + validateReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ValidationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + submitReviewReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + returnToDraftReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + archiveReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + restoreReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PublishRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioQuestions: { + parameters: { + query?: { + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionIndexPage"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getQuestionForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QuestionUpdateBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + addQuestionUpdate: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QuestionUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionUpdateResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateQuestionUpdate: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + updateId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QuestionUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionUpdateResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteQuestionUpdate: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + updateId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + resolveQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ResolveQuestionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ResolveQuestionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + startQuestionInvestigation: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + pauseQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + resumeQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + reopenQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + archiveQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PublishRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioProjects: { + parameters: { + query?: { + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectIndexPageEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + createProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponseEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + getProjectForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectEditResponseEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + updateProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProjectUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectEditResponseEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + deleteProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + changeProjectPhase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ChangeProjectPhaseRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PublishRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioProjectDecisions: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"][]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDecisionRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getProjectDecision: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DecisionUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + acceptProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + rejectProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + supersedeProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SupersedeDecisionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioReleases: { + parameters: { + query?: { + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseIndexPage"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getReleaseForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ReleaseUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + archiveRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioTopics: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TopicEditListEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + createTopic: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TopicEdit"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TopicEditEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + updateTopic: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TopicEdit"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TopicEditEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + deleteTopic: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + listStudioTags: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TagEdit"][]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createTag: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TagEdit"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TagEdit"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateTag: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TagEdit"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TagEdit"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteTag: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getStudioSite: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SiteConfigResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateStudioSite: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SiteConfigRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SiteConfigResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getStudioProfile: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProfileEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateStudioProfile: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProfileEditRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProfileEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishProfile: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishProfile: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProfileEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getHomeFocus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HomeFocusResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateHomeFocus: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["HomeFocusRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HomeFocusResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioProjectActivities: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectActivityResponse"][]; + }; + }; + /** @description 401 */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 403 */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 404 */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 500 */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createProjectActivity: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProjectActivityRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectActivityResponse"]; + }; + }; + /** @description 400 */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 401 */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 403 */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 404 */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 409 */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 422 */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 500 */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateProjectActivity: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + activityId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateProjectActivityRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectActivityResponse"]; + }; + }; + /** @description 400 */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 401 */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 403 */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 404 */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 409 */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 422 */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 500 */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; +} diff --git a/src/features/tech-log/contracts/management/management-api.openapi.yaml b/src/features/tech-log/contracts/management/management-api.openapi.yaml new file mode 100644 index 0000000..0a65f79 --- /dev/null +++ b/src/features/tech-log/contracts/management/management-api.openapi.yaml @@ -0,0 +1,7065 @@ +openapi: 3.1.0 +info: + title: Tech Log Studio Management API (Secondary) + version: 1.0.0 + description: |- + ⚠ 봉투 결정(ADR-006) 부분 반영 — 이 파일에는 두 모양이 공존한다. + + topics 4개와 projects 5개는 studio-v1.yaml과 같은 방식으로 변환했다 + (`application/json` + ErrorEnvelope / Envelope). 그 9개가 첫 구현 + 대상이자 첫 소비자이기 때문이다. + + 나머지 70개는 아직 bare payload + `application/problem+json` + ProblemDetails + 다. 구현에 착수할 때 같은 방식으로 따라온다 — 소비자가 없는 operation을 미리 + 변환해 두면 검증되지 않은 모양이 계약에 고정된다. + + Tech Log의 유형별 specialized management 계약이다. + + 이 계약은 primary Studio orchestration 계약(`studio-v1.yaml`)이 아니다. + 현재 Studio UI는 이 operation들을 직접 호출하지 않는다. + + 존재 이유는 설계 수정 가이드 14장과 23.7의 규칙이다. + + > 현재 UI에서 사용하지 않는다는 이유로 Backend capability를 삭제하지 않는다. + > 다만 capability 보존과 endpoint 보존을 동일시하지 않는다. + + 따라서 Question start/pause/resume/reopen/archive, Project phase change, + Project Activity, Release, Topic/Tag, Profile/Site/Home Focus, + Decision supersede/reject 등의 Domain/Application capability를 여기에 보존하고, + 현재 Frontend가 사용하는 통합 편집 흐름은 `studio-v1.yaml`이 소유한다. + + ## 이 계약을 사용하는 규칙 + + - primary 흐름(작업본 편집·검증·미리보기·게시)은 반드시 `studio-v1.yaml`을 사용한다. + - 여기의 publish/unpublish 계열 operation은 primary `publishStudioDocument`와 + 동일한 Publication Event/Snapshot 경로를 거쳐야 한다. + 서로 다른 두 개의 게시 경로를 허용하지 않는다. + - 새 UI가 특정 capability를 실제로 사용하기 시작하면 해당 operation을 + primary 계약으로 승격하고 여기에서 제거한다. + + ## 이 계약에서 제거된 것 + + - `GET /session` : primary `studio-v1.yaml`이 소유한다. + - `/documents` 계열 : primary 통합 Working Copy operation으로 대체되었다. + - `/assets` 계열 : primary `studio-v1.yaml`이 소유한다. + - `/previews`, `/previews/{token}` : Preview Token 계약은 폐기되었다. + 인증된 Preview Artifact(`studio-v1.yaml`)로 대체되었다. +servers: +- url: / +security: +- sessionCookie: [] +tags: +- name: Cases +- name: References +- name: Questions +- name: Projects +- name: Decisions +- name: Releases +- name: Taxonomy +- name: Identity +paths: + /api/v1/studio/cases: + post: + operationId: createCaseDraft + tags: + - Cases + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}: + get: + operationId: getCaseForEdit + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateCaseDraft + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CaseUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteCaseDraft + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references: + post: + operationId: createReferenceDraft + tags: + - References + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}: + get: + operationId: getReferenceForEdit + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateReferenceDraft + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteReferenceDraft + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/validate: + post: + operationId: validateCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/submit-review: + post: + operationId: submitReviewCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/return-to-draft: + post: + operationId: returnToDraftCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/unpublish: + post: + operationId: unpublishCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/archive: + post: + operationId: archiveCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/restore: + post: + operationId: restoreCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/publish: + post: + operationId: publishCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/validate: + post: + operationId: validateReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/submit-review: + post: + operationId: submitReviewReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/return-to-draft: + post: + operationId: returnToDraftReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/unpublish: + post: + operationId: unpublishReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/archive: + post: + operationId: archiveReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/restore: + post: + operationId: restoreReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/publish: + post: + operationId: publishReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions: + post: + operationId: createQuestion + tags: + - Questions + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + get: + operationId: listStudioQuestions + tags: + - Questions + parameters: + - name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + - name: size + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionIndexPage' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}: + get: + operationId: getQuestionForEdit + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateBody' + security: + - sessionCookie: [] + delete: + operationId: deleteQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/updates: + post: + operationId: addQuestionUpdate + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/updates/{updateId}: + put: + operationId: updateQuestionUpdate + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: updateId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteQuestionUpdate + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: updateId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/resolve: + post: + operationId: resolveQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ResolveQuestionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ResolveQuestionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/start-investigation: + post: + operationId: startQuestionInvestigation + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/pause: + post: + operationId: pauseQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/resume: + post: + operationId: resumeQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/reopen: + post: + operationId: reopenQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/archive: + post: + operationId: archiveQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/publish: + post: + operationId: publishQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/unpublish: + post: + operationId: unpublishQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects: + post: + operationId: createProject + tags: + - Projects + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponseEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + get: + operationId: listStudioProjects + tags: + - Projects + parameters: + - name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + - name: size + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectIndexPageEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}: + get: + operationId: getProjectForEdit + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectEditResponseEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + security: + - sessionCookie: [] + put: + operationId: updateProject + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectEditResponseEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteProject + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/change-phase: + post: + operationId: changeProjectPhase + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeProjectPhaseRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/publish: + post: + operationId: publishProject + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/unpublish: + post: + operationId: unpublishProject + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions: + post: + operationId: createProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDecisionRequest' + security: + - sessionCookie: [] + get: + operationId: listStudioProjectDecisions + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions/{decisionId}: + get: + operationId: getProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionUpdateRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions/{decisionId}/accept: + post: + operationId: acceptProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions/{decisionId}/reject: + post: + operationId: rejectProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions/{decisionId}/supersede: + post: + operationId: supersedeProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SupersedeDecisionRequest' + security: + - sessionCookie: [] + /api/v1/studio/releases: + post: + operationId: createRelease + tags: + - Releases + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + get: + operationId: listStudioReleases + tags: + - Releases + parameters: + - name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + - name: size + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseIndexPage' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + /api/v1/studio/releases/{id}: + get: + operationId: getReleaseForEdit + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateRelease + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteRelease + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/releases/{id}/publish: + post: + operationId: publishRelease + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/releases/{id}/archive: + post: + operationId: archiveRelease + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/topics: + get: + operationId: listStudioTopics + tags: + - Taxonomy + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEditListEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + security: + - sessionCookie: [] + post: + operationId: createTopic + tags: + - Taxonomy + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEditEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEdit' + security: + - sessionCookie: [] + /api/v1/studio/topics/{id}: + put: + operationId: updateTopic + tags: + - Taxonomy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEditEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEdit' + security: + - sessionCookie: [] + delete: + operationId: deleteTopic + tags: + - Taxonomy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/tags: + get: + operationId: listStudioTags + tags: + - Taxonomy + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TagEdit' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + post: + operationId: createTag + tags: + - Taxonomy + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TagEdit' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagEdit' + security: + - sessionCookie: [] + /api/v1/studio/tags/{id}: + put: + operationId: updateTag + tags: + - Taxonomy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TagEdit' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagEdit' + security: + - sessionCookie: [] + delete: + operationId: deleteTag + tags: + - Taxonomy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/site: + get: + operationId: getStudioSite + tags: + - Identity + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SiteConfigResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateStudioSite + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SiteConfigResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SiteConfigRequest' + security: + - sessionCookie: [] + /api/v1/studio/profile: + get: + operationId: getStudioProfile + tags: + - Identity + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProfileEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateStudioProfile + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProfileEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProfileEditRequest' + security: + - sessionCookie: [] + /api/v1/studio/profile/publish: + post: + operationId: publishProfile + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/profile/unpublish: + post: + operationId: unpublishProfile + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProfileEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/home-focus: + get: + operationId: getHomeFocus + tags: + - Identity + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/HomeFocusResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateHomeFocus + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/HomeFocusResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/HomeFocusRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/activities: + get: + operationId: listStudioProjectActivities + tags: + - Projects + security: + - sessionCookie: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProjectActivityResponse' + '401': + description: '401' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: '403' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: '404' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: '500' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + post: + operationId: createProjectActivity + tags: + - Projects + security: + - sessionCookie: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectActivityRequest' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectActivityResponse' + '400': + description: '400' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: '401' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: '403' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: '404' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: '409' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: '422' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: '500' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + /api/v1/studio/projects/{id}/activities/{activityId}: + put: + operationId: updateProjectActivity + tags: + - Projects + security: + - sessionCookie: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: activityId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProjectActivityRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectActivityResponse' + '400': + description: '400' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: '401' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: '403' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: '404' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: '409' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: '422' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: '500' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' +components: + securitySchemes: + sessionCookie: + type: apiKey + in: cookie + name: TECHLOG_SESSION + parameters: + CsrfToken: + name: X-CSRF-TOKEN + in: header + required: true + schema: + type: string + schemas: + # ------------------------------------------------------------------------- + # 응답 봉투 (ADR-006) + # + # 이 파일 상단이 예고한 변환이다. 지금은 topics/projects 9개 operation 만 + # 옮겼다 — 그 둘이 첫 소비자이기 때문이고, 나머지는 구현에 착수할 때 같은 + # 방식으로 따라온다. 그동안 이 파일에는 두 모양이 공존한다: 변환된 + # operation 은 `application/json` + Envelope, 나머지는 예전 + # `application/problem+json` + ProblemDetails 다. + # ------------------------------------------------------------------------- + ResponseMeta: + type: object + additionalProperties: false + required: + - requestId + - traceId + properties: + requestId: + type: string + minLength: 1 + maxLength: 200 + traceId: + type: string + minLength: 1 + maxLength: 200 + correlationId: + type: + - string + - 'null' + maxLength: 200 + page: + type: + - object + - 'null' + additionalProperties: true + description: 백엔드 템플릿의 ResponseMeta record 가 직렬화하는 자리다. 페이지 정보는 payload 의 `page` 가 소유하므로 이 관리 표면에서는 항상 null 이다. + ApiError: + type: object + additionalProperties: false + required: + - code + - category + - message + - retryable + properties: + code: + type: string + enum: + - AUTHENTICATION_REQUIRED + - STUDIO_ACCESS_DENIED + - REQUEST_VALIDATION_FAILED + - VERSION_CONFLICT + - TOPIC_NOT_FOUND + - TOPIC_NAME_TAKEN + - TOPIC_SLUG_TAKEN + - TOPIC_IN_USE + - PROJECT_NOT_FOUND + - PROJECT_SLUG_TAKEN + - PROJECT_IN_USE + - INTERNAL_ERROR + description: '`INTERNAL_ERROR` 는 이 기능이 아니라 스켈레톤의 공통 처리기가 내는 코드다. 계약이 그것까지 열거해야 500 응답이 계약을 벗어나지 않는다.' + category: + type: string + enum: + - VALIDATION + - AUTH + - AUTHZ + - NOT_FOUND + - CONFLICT + - RATE_LIMIT + - TRANSIENT_DEPENDENCY + - PERMANENT_DEPENDENCY + - DATA_INTEGRITY + - INTERNAL + message: + type: string + minLength: 1 + maxLength: 5000 + retryable: + type: boolean + details: + oneOf: + - $ref: '#/components/schemas/ValidationErrorDetails' + - $ref: '#/components/schemas/VersionConflictDetails' + - type: 'null' + ValidationErrorDetails: + type: object + additionalProperties: false + required: + - fieldErrors + properties: + fieldErrors: + type: array + maxItems: 200 + items: + $ref: '#/components/schemas/FieldError' + VersionConflictDetails: + type: object + additionalProperties: false + required: + - currentVersion + properties: + currentVersion: + type: integer + format: int64 + minimum: 0 + ErrorEnvelope: + type: object + additionalProperties: false + required: + - success + - error + - meta + properties: + success: + type: boolean + const: false + error: + $ref: '#/components/schemas/ApiError' + meta: + $ref: '#/components/schemas/ResponseMeta' + TopicEditEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + $ref: '#/components/schemas/TopicEdit' + meta: + $ref: '#/components/schemas/ResponseMeta' + TopicEditListEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + type: array + maxItems: 500 + items: + $ref: '#/components/schemas/TopicEdit' + meta: + $ref: '#/components/schemas/ResponseMeta' + CreateDraftResponseEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + $ref: '#/components/schemas/CreateDraftResponse' + meta: + $ref: '#/components/schemas/ResponseMeta' + ProjectIndexPageEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + $ref: '#/components/schemas/ProjectIndexPage' + meta: + $ref: '#/components/schemas/ResponseMeta' + ProjectEditResponseEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + $ref: '#/components/schemas/ProjectEditResponse' + meta: + $ref: '#/components/schemas/ResponseMeta' + FieldError: + type: object + required: + - field + - code + - message + properties: + field: + type: string + code: + type: string + message: + type: string + ProblemDetails: + type: object + required: + - type + - title + - status + - code + - detail + - instance + - traceId + properties: + type: + type: string + title: + type: string + status: + type: integer + code: + type: string + detail: + type: string + instance: + type: string + traceId: + type: string + fieldErrors: + type: array + items: + $ref: '#/components/schemas/FieldError' + currentVersion: + type: integer + format: int64 + PageMetadata: + type: object + required: + - number + - size + - totalElements + - totalPages + - hasPrevious + - hasNext + properties: + number: + type: integer + minimum: 1 + size: + type: integer + minimum: 1 + maximum: 100 + totalElements: + type: integer + format: int64 + minimum: 0 + totalPages: + type: integer + minimum: 0 + hasPrevious: + type: boolean + hasNext: + type: boolean + TagSummary: + type: object + required: + - name + - slug + properties: + name: + type: string + slug: + type: string + ProjectSummary: + type: object + required: + - name + - slug + - path + properties: + name: + type: string + slug: + type: string + path: + type: string + AssetReference: + type: object + required: + - assetId + - url + properties: + assetId: + type: string + format: uuid + url: + type: string + altText: + type: string + width: + type: integer + height: + type: integer + contentType: + type: string + RelatedEntry: + type: object + required: + - type + - title + - path + properties: + type: + type: string + enum: + - CASE + - REFERENCE + - QUESTION + - PROJECT + - PROJECT_DECISION + - RELEASE + title: + type: string + summary: + type: string + path: + type: string + PublicationStatus: + type: object + required: + - state + - hasUnpublishedChanges + properties: + state: + type: string + enum: + - NEVER_PUBLISHED + - ACTIVE + - WITHDRAWN + publishedSourceVersion: + type: integer + format: int64 + hasUnpublishedChanges: + type: boolean + canonicalPath: + type: string + publishedAt: + type: string + format: date-time + ValidationIssue: + type: object + required: + - severity + - code + - message + properties: + severity: + type: string + enum: + - ERROR + - WARNING + code: + type: string + field: + type: string + message: + type: string + ValidationResponse: + type: object + required: + - validForSave + - validForPublish + - issues + properties: + validForSave: + type: boolean + validForPublish: + type: boolean + issues: + type: array + items: + $ref: '#/components/schemas/ValidationIssue' + CreateDraftRequest: + type: object + required: + - title + properties: + title: + type: string + maxLength: 180 + CreateDraftResponse: + type: object + required: + - id + - status + - version + - createdAt + properties: + id: + type: string + format: uuid + status: + type: string + enum: + - DRAFT + version: + type: integer + format: int64 + createdAt: + type: string + format: date-time + RelationEdit: + type: object + properties: + primaryProjectId: + type: string + format: uuid + relatedProjectIds: + type: array + items: + type: string + format: uuid + originQuestionId: + type: string + format: uuid + relatedDocumentIds: + type: array + items: + type: string + format: uuid + derivedReferenceIds: + type: array + items: + type: string + format: uuid + CaseUpdateRequest: + type: object + required: + - expectedVersion + - title + - problemSummary + - conclusionSummary + - content + - contentFormatVersion + - tagIds + - relations + properties: + expectedVersion: + type: integer + format: int64 + title: + type: string + maxLength: 180 + slug: + type: string + maxLength: 180 + problemSummary: + type: string + maxLength: 600 + conclusionSummary: + type: string + maxLength: 600 + environmentSummary: + type: array + items: + type: string + content: + type: string + maxLength: 2000000 + contentFormatVersion: + type: integer + minimum: 1 + primaryTopicId: + type: string + format: uuid + tagIds: + type: array + maxItems: 12 + items: + type: string + format: uuid + relations: + $ref: '#/components/schemas/RelationEdit' + coverAssetId: + type: string + format: uuid + lastVerifiedAt: + type: string + format: date-time + targetVisibility: + type: string + enum: + - PRIVATE + - UNLISTED + - PUBLIC + CaseEditResponse: + type: object + required: + - id + - version + - title + - problemSummary + - conclusionSummary + - content + - contentFormatVersion + - tagIds + - relations + - workflowStatus + - targetVisibility + - publication + - updatedAt + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + title: + type: string + slug: + type: string + problemSummary: + type: string + conclusionSummary: + type: string + environmentSummary: + type: array + items: + type: string + content: + type: string + contentFormatVersion: + type: integer + primaryTopicId: + type: string + format: uuid + tagIds: + type: array + items: + type: string + format: uuid + relations: + $ref: '#/components/schemas/RelationEdit' + coverAssetId: + type: string + format: uuid + lastVerifiedAt: + type: string + format: date-time + workflowStatus: + type: string + targetVisibility: + type: string + publication: + $ref: '#/components/schemas/PublicationStatus' + updatedAt: + type: string + format: date-time + ReferenceUpdateRequest: + type: object + required: + - expectedVersion + - title + - scopeSummary + - appliesTo + - excludedScope + - freshnessStatus + - content + - contentFormatVersion + - tagIds + - relations + properties: + expectedVersion: + type: integer + format: int64 + title: + type: string + maxLength: 180 + slug: + type: string + maxLength: 180 + scopeSummary: + type: string + maxLength: 600 + appliesTo: + type: array + items: + type: string + excludedScope: + type: array + items: + type: string + freshnessStatus: + type: string + enum: + - CURRENT + - REVIEW_DUE + - HISTORICAL + content: + type: string + maxLength: 2000000 + contentFormatVersion: + type: integer + minimum: 1 + primaryTopicId: + type: string + format: uuid + tagIds: + type: array + maxItems: 12 + items: + type: string + format: uuid + relations: + $ref: '#/components/schemas/RelationEdit' + coverAssetId: + type: string + format: uuid + lastVerifiedAt: + type: string + format: date-time + targetVisibility: + type: string + enum: + - PRIVATE + - UNLISTED + - PUBLIC + ReferenceEditResponse: + type: object + required: + - id + - version + - title + - scopeSummary + - appliesTo + - excludedScope + - freshnessStatus + - content + - contentFormatVersion + - tagIds + - relations + - workflowStatus + - targetVisibility + - publication + - updatedAt + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + title: + type: string + slug: + type: string + scopeSummary: + type: string + appliesTo: + type: array + items: + type: string + excludedScope: + type: array + items: + type: string + freshnessStatus: + type: string + content: + type: string + contentFormatVersion: + type: integer + primaryTopicId: + type: string + format: uuid + tagIds: + type: array + items: + type: string + format: uuid + relations: + $ref: '#/components/schemas/RelationEdit' + coverAssetId: + type: string + format: uuid + lastVerifiedAt: + type: string + format: date-time + workflowStatus: + type: string + targetVisibility: + type: string + publication: + $ref: '#/components/schemas/PublicationStatus' + updatedAt: + type: string + format: date-time + ExpectedVersionRequest: + type: object + required: + - expectedVersion + properties: + expectedVersion: + type: integer + format: int64 + PublishRequest: + type: object + required: + - expectedVersion + - visibility + properties: + expectedVersion: + type: integer + format: int64 + visibility: + type: string + enum: + - PUBLIC + - UNLISTED + PublishResponse: + type: object + required: + - id + - status + - visibility + - canonicalPath + - publishedAt + - version + properties: + id: + type: string + format: uuid + status: + type: string + enum: + - PUBLISHED + visibility: + type: string + enum: + - PUBLIC + - UNLISTED + canonicalPath: + type: string + publishedAt: + type: string + format: date-time + version: + type: integer + format: int64 + QuestionPointEdit: + type: object + required: + - kind + - content + - displayOrder + properties: + id: + type: string + format: uuid + kind: + type: string + enum: + - FACT + - ASSUMPTION + - UNKNOWN + - CONSTRAINT + content: + type: string + displayOrder: + type: integer + minimum: 0 + QuestionUpdateRequest: + type: object + required: + - expectedVersion + - type + - title + - bodyMarkdown + - visibility + - occurredAt + properties: + type: + type: string + enum: + - OBSERVATION + - EVIDENCE + - SCOPE_CHANGE + - BLOCKER + - NEXT_STEP + - RESOLUTION + - RESOLUTION_REOPENED + title: + type: string + maxLength: 180 + bodyMarkdown: + type: string + visibility: + type: string + enum: + - PRIVATE + - PUBLIC + occurredAt: + type: string + format: date-time + expectedVersion: + type: integer + format: int64 + QuestionUpdateResponse: + type: object + required: + - id + - questionVersion + - type + - title + - bodyMarkdown + - visibility + - occurredAt + properties: + id: + type: string + format: uuid + questionVersion: + type: integer + format: int64 + type: + type: string + title: + type: string + bodyMarkdown: + type: string + visibility: + type: string + occurredAt: + type: string + format: date-time + QuestionUpdateBody: + type: object + required: + - expectedVersion + - question + - summary + - context + - importance + - points + - tagIds + properties: + expectedVersion: + type: integer + format: int64 + question: + type: string + maxLength: 300 + slug: + type: string + maxLength: 180 + summary: + type: string + maxLength: 600 + context: + type: string + importance: + type: string + nextVerification: + type: string + targetVisibility: + type: string + enum: + - PRIVATE + - UNLISTED + - PUBLIC + primaryProjectId: + type: string + format: uuid + topicId: + type: string + format: uuid + tagIds: + type: array + maxItems: 12 + items: + type: string + format: uuid + points: + type: array + items: + $ref: '#/components/schemas/QuestionPointEdit' + QuestionEditResponse: + type: object + required: + - id + - version + - question + - status + - targetVisibility + - points + - updates + - publication + - updatedAt + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + question: + type: string + slug: + type: string + summary: + type: string + context: + type: string + importance: + type: string + nextVerification: + type: string + status: + type: string + targetVisibility: + type: string + primaryProjectId: + type: string + format: uuid + topicId: + type: string + format: uuid + tagIds: + type: array + items: + type: string + format: uuid + points: + type: array + items: + $ref: '#/components/schemas/QuestionPointEdit' + updates: + type: array + items: + $ref: '#/components/schemas/QuestionUpdateResponse' + resolution: + type: object + properties: + type: + type: string + summary: + type: string + resolvedAt: + type: string + format: date-time + publication: + $ref: '#/components/schemas/PublicationStatus' + updatedAt: + type: string + format: date-time + ResolveQuestionRequest: + type: object + required: + - expectedVersion + - resolutionType + - resolutionSummary + - resolvedAt + - createCaseDraft + - createProjectDecision + properties: + expectedVersion: + type: integer + format: int64 + resolutionType: + type: string + enum: + - DECISION_MADE + - ASSUMPTION_REJECTED + - QUESTION_REFRAMED + - NO_LONGER_RELEVANT + resolutionSummary: + type: string + resolvedAt: + type: string + format: date-time + createCaseDraft: + type: boolean + createProjectDecision: + type: boolean + ResolveQuestionResponse: + type: object + required: + - questionId + - status + - version + properties: + questionId: + type: string + format: uuid + status: + type: string + enum: + - RESOLVED + version: + type: integer + format: int64 + createdCaseDraftId: + type: string + format: uuid + createdProjectDecisionId: + type: string + format: uuid + ProjectUpdateRequest: + type: object + required: + - expectedVersion + - name + - oneLinePurpose + - purposeMarkdown + - boundaryMarkdown + - phase + - technologyLabels + - targetVisibility + properties: + expectedVersion: + type: integer + format: int64 + name: + type: string + maxLength: 180 + slug: + type: string + maxLength: 180 + oneLinePurpose: + type: string + maxLength: 600 + purposeMarkdown: + type: string + boundaryMarkdown: + type: string + systemOverviewMarkdown: + type: string + phase: + type: string + currentObjective: + type: string + nextStep: + type: string + technologyLabels: + type: array + items: + type: string + targetVisibility: + type: string + enum: + - PRIVATE + - UNLISTED + - PUBLIC + featuredOrder: + type: integer + topicIds: + type: array + items: + type: string + format: uuid + documentLinks: + type: array + items: + $ref: '#/components/schemas/ProjectResourceLinkEdit' + questionLinks: + type: array + items: + $ref: '#/components/schemas/ProjectResourceLinkEdit' + ProjectEditResponse: + type: object + required: + - id + - version + - name + - phase + - workflowStatus + - targetVisibility + - publication + - updatedAt + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + name: + type: string + slug: + type: string + oneLinePurpose: + type: string + purposeMarkdown: + type: string + boundaryMarkdown: + type: string + systemOverviewMarkdown: + type: string + phase: + type: string + currentObjective: + type: string + nextStep: + type: string + technologyLabels: + type: array + items: + type: string + workflowStatus: + type: string + targetVisibility: + type: string + featuredOrder: + type: integer + publication: + $ref: '#/components/schemas/PublicationStatus' + updatedAt: + type: string + format: date-time + topicIds: + type: array + items: + type: string + format: uuid + documentLinks: + type: array + items: + $ref: '#/components/schemas/ProjectResourceLinkEdit' + questionLinks: + type: array + items: + $ref: '#/components/schemas/ProjectResourceLinkEdit' + ChangeProjectPhaseRequest: + type: object + required: + - expectedVersion + - phase + - reason + - publishActivity + properties: + expectedVersion: + type: integer + format: int64 + phase: + type: string + reason: + type: string + publishActivity: + type: boolean + DecisionUpdateRequest: + type: object + required: + - expectedVersion + - statement + - rationaleMarkdown + - consequences + - alternativesMarkdown + - targetVisibility + properties: + expectedVersion: + type: integer + format: int64 + statement: + type: string + maxLength: 1000 + rationaleMarkdown: + type: string + consequences: + type: array + items: + type: string + alternativesMarkdown: + type: string + targetVisibility: + type: string + enum: + - PRIVATE + - PUBLIC + sourceQuestionId: + type: string + format: uuid + sourceCaseId: + type: string + format: uuid + isFeatured: + type: boolean + decidedAt: + type: string + format: date-time + DecisionResponse: + allOf: + - $ref: '#/components/schemas/DecisionUpdateRequest' + - type: object + required: + - id + - projectId + - status + - version + properties: + id: + type: string + format: uuid + projectId: + type: string + format: uuid + status: + type: string + version: + type: integer + format: int64 + supersededById: + type: string + format: uuid + ReleaseUpdateRequest: + type: object + required: + - expectedVersion + - versionLabel + - title + - summary + - changeTypes + - changesMarkdown + - verificationMarkdown + properties: + expectedVersion: + type: integer + format: int64 + versionLabel: + type: string + maxLength: 32 + title: + type: string + maxLength: 180 + summary: + type: string + maxLength: 600 + releasedOn: + type: string + format: date + changeTypes: + type: array + items: + type: string + reasonMarkdown: + type: string + changesMarkdown: + type: string + userImpactMarkdown: + type: string + implementationImpactMarkdown: + type: string + verificationMarkdown: + type: string + knownLimitationsMarkdown: + type: string + relatedResources: + type: array + items: + type: object + required: + - resourceType + - resourceId + properties: + resourceType: + type: string + resourceId: + type: string + format: uuid + ReleaseEditResponse: + allOf: + - $ref: '#/components/schemas/ReleaseUpdateRequest' + - type: object + required: + - id + - workflowStatus + - version + - publication + properties: + id: + type: string + format: uuid + workflowStatus: + type: string + version: + type: integer + format: int64 + publication: + $ref: '#/components/schemas/PublicationStatus' + TopicEdit: + type: object + required: + - name + - slug + properties: + id: + type: string + format: uuid + description: |- + 응답에만 실린다. 경로가 `/topics/{id}`이므로 이 값이 없으면 목록에서 어떤 주제도 + 주소지정할 수 없다 — ProjectEditResponse / CaseEditResponse는 같은 이유로 이미 + id를 싣고 있었고, TopicEdit만 빠져 있었다. 쓰기 요청에 와도 서버는 무시한다; + 수정 대상은 경로가 소유한다. + version: + type: integer + format: int64 + description: |- + 응답에만 실린다. 다음 수정에서 보낼 `expectedVersion`의 출처다. 이것이 없으면 + 클라이언트가 낙관적 잠금에 쓸 값을 얻을 방법이 없다. + name: + type: string + maxLength: 80 + slug: + type: string + maxLength: 100 + description: + type: string + maxLength: 600 + scope: + type: string + status: + type: string + enum: + - ACTIVE + - ARCHIVED + expectedVersion: + type: integer + format: int64 + featuredReferenceId: + type: string + format: uuid + featuredCaseIds: + type: array + maxItems: 3 + items: + type: string + format: uuid + TagEdit: + type: object + required: + - name + - slug + properties: + name: + type: string + maxLength: 40 + slug: + type: string + maxLength: 60 + expectedVersion: + type: integer + format: int64 + SiteConfigRequest: + type: object + required: + - expectedVersion + - brandTitle + - identityStatement + - operatorDisplayName + - contacts + properties: + expectedVersion: + type: integer + format: int64 + brandTitle: + type: string + identityStatement: + type: string + operatorDisplayName: + type: string + shortIdentity: + type: string + avatarAssetId: + type: string + format: uuid + contacts: + type: array + items: + type: object + ProfileEditRequest: + type: object + required: + - expectedVersion + - headline + - workingModel + - territories + - selectedEvidence + - trajectory + - contacts + - targetVisibility + properties: + expectedVersion: + type: integer + format: int64 + headline: + type: string + introductionMarkdown: + type: string + workingModel: + type: array + items: + type: object + territories: + type: array + items: + type: object + selectedEvidence: + type: array + items: + type: object + trajectory: + type: array + items: + type: object + contacts: + type: array + items: + type: object + targetVisibility: + type: string + enum: + - PRIVATE + - PUBLIC + HomeFocusRequest: + type: object + required: + - expectedVersion + properties: + expectedVersion: + type: integer + format: int64 + defaultType: + type: string + enum: + - CURRENT_WORK + - OPEN_QUESTION + - RECENT_DECISION + currentProjectId: + type: string + format: uuid + openQuestionId: + type: string + format: uuid + recentDecisionId: + type: string + format: uuid + QuestionIndexItem: + type: object + required: + - id + - question + - status + - targetVisibility + - updatedAt + - version + - publication + properties: + id: + type: string + format: uuid + question: + type: string + status: + type: string + targetVisibility: + type: string + primaryProject: + $ref: '#/components/schemas/ProjectSummary' + nextVerification: + type: string + updatedAt: + type: string + format: date-time + version: + type: integer + format: int64 + publication: + $ref: '#/components/schemas/PublicationStatus' + QuestionIndexPage: + type: object + required: + - items + - page + properties: + items: + type: array + items: + $ref: '#/components/schemas/QuestionIndexItem' + page: + $ref: '#/components/schemas/PageMetadata' + ProjectIndexItem: + type: object + required: + - id + - name + - phase + - workflowStatus + - targetVisibility + - updatedAt + - version + - publication + properties: + id: + type: string + format: uuid + name: + type: string + phase: + type: string + workflowStatus: + type: string + targetVisibility: + type: string + currentObjective: + type: string + nextStep: + type: string + updatedAt: + type: string + format: date-time + version: + type: integer + format: int64 + publication: + $ref: '#/components/schemas/PublicationStatus' + ProjectIndexPage: + type: object + required: + - items + - page + properties: + items: + type: array + items: + $ref: '#/components/schemas/ProjectIndexItem' + page: + $ref: '#/components/schemas/PageMetadata' + ReleaseIndexItem: + type: object + required: + - id + - versionLabel + - title + - workflowStatus + - updatedAt + - version + - publication + properties: + id: + type: string + format: uuid + versionLabel: + type: string + title: + type: string + releasedOn: + type: string + format: date + workflowStatus: + type: string + updatedAt: + type: string + format: date-time + version: + type: integer + format: int64 + publication: + $ref: '#/components/schemas/PublicationStatus' + ReleaseIndexPage: + type: object + required: + - items + - page + properties: + items: + type: array + items: + $ref: '#/components/schemas/ReleaseIndexItem' + page: + $ref: '#/components/schemas/PageMetadata' + SiteConfigResponse: + type: object + required: + - id + - version + - brandTitle + - identityStatement + - operatorDisplayName + - contacts + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + brandTitle: + type: string + identityStatement: + type: string + operatorDisplayName: + type: string + shortIdentity: + type: string + avatarAssetId: + type: string + format: uuid + contacts: + type: array + items: + type: object + ProfileEditResponse: + type: object + required: + - id + - version + - headline + - workingModel + - territories + - selectedEvidence + - trajectory + - contacts + - targetVisibility + - publication + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + headline: + type: string + introductionMarkdown: + type: string + workingModel: + type: array + items: + type: object + territories: + type: array + items: + type: object + selectedEvidence: + type: array + items: + type: object + trajectory: + type: array + items: + type: object + contacts: + type: array + items: + type: object + targetVisibility: + type: string + publication: + $ref: '#/components/schemas/PublicationStatus' + HomeFocusResponse: + type: object + required: + - id + - version + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + defaultType: + type: string + currentProjectId: + type: string + format: uuid + openQuestionId: + type: string + format: uuid + recentDecisionId: + type: string + format: uuid + ProjectResourceLinkEdit: + type: object + required: + - resourceId + - relationType + properties: + resourceId: + type: string + format: uuid + relationType: + type: string + enum: + - PRIMARY + - RELATED + featuredOrder: + type: integer + minimum: 0 + CreateDecisionRequest: + type: object + required: + - expectedProjectVersion + - statement + - rationaleMarkdown + - consequences + - alternativesMarkdown + - targetVisibility + properties: + expectedProjectVersion: + type: integer + format: int64 + statement: + type: string + maxLength: 1000 + rationaleMarkdown: + type: string + consequences: + type: array + items: + type: string + alternativesMarkdown: + type: string + targetVisibility: + type: string + enum: + - PRIVATE + - PUBLIC + sourceQuestionId: + type: string + format: uuid + sourceCaseId: + type: string + format: uuid + isFeatured: + type: boolean + decidedAt: + type: string + format: date-time + SupersedeDecisionRequest: + type: object + required: + - expectedVersion + - supersededById + properties: + expectedVersion: + type: integer + format: int64 + supersededById: + type: string + format: uuid + ProjectActivityRequest: + type: object + required: + - expectedProjectVersion + - activityType + - title + - visibility + - occurredAt + properties: + expectedProjectVersion: + type: integer + format: int64 + activityType: + type: string + title: + type: string + maxLength: 180 + summary: + type: string + maxLength: 600 + visibility: + type: string + enum: + - PRIVATE + - PUBLIC + relatedResourceType: + type: string + relatedResourceId: + type: string + format: uuid + occurredAt: + type: string + format: date-time + ProjectActivityResponse: + type: object + required: + - id + - projectId + - activityType + - title + - visibility + - origin + - occurredAt + - version + properties: + id: + type: string + format: uuid + projectId: + type: string + format: uuid + activityType: + type: string + title: + type: string + summary: + type: string + visibility: + type: string + origin: + type: string + relatedResourceType: + type: string + relatedResourceId: + type: string + format: uuid + occurredAt: + type: string + format: date-time + version: + type: integer + format: int64 + UpdateProjectActivityRequest: + type: object + required: + - expectedVersion + - title + - visibility + - occurredAt + properties: + expectedVersion: + type: integer + format: int64 + title: + type: string + summary: + type: string + visibility: + type: string + enum: + - PRIVATE + - PUBLIC + occurredAt: + type: string + format: date-time diff --git a/src/features/tech-log/contracts/public/canonical-source.json b/src/features/tech-log/contracts/public/canonical-source.json index f39638e..1b36765 100644 --- a/src/features/tech-log/contracts/public/canonical-source.json +++ b/src/features/tech-log/contracts/public/canonical-source.json @@ -2,7 +2,7 @@ "packageId": "@tech-log/public-contract", "version": "2.0.0", "digest": "sha256:8ac71425b38658f34641102b4c2e6e21288c811efebdb92c0a46fb9d4790e23e", - "sourceRevision": "b98eaf9", + "sourceRevision": "6ef5c1c", "operationIds": [ "getPublicSite", "getPublicHome", diff --git a/src/features/tech-log/contracts/studio/canonical-source.json b/src/features/tech-log/contracts/studio/canonical-source.json index 259f6df..cf93a8e 100644 --- a/src/features/tech-log/contracts/studio/canonical-source.json +++ b/src/features/tech-log/contracts/studio/canonical-source.json @@ -2,7 +2,7 @@ "packageId": "@tech-log/studio-contract", "version": "3.0.0", "digest": "sha256:6cae9924403d0761f401643a022980b8e04183eea0d890c143c9fbbbbc7431e4", - "sourceRevision": "b98eaf9", + "sourceRevision": "6ef5c1c", "operationIds": [ "getStudioSession", "getStudioDashboard", diff --git a/src/features/tech-log/contracts/tech-log-management-contract-contribution.ts b/src/features/tech-log/contracts/tech-log-management-contract-contribution.ts new file mode 100644 index 0000000..2dc5c2a --- /dev/null +++ b/src/features/tech-log/contracts/tech-log-management-contract-contribution.ts @@ -0,0 +1,256 @@ +import type { + CommandEffectDescriptor, + InstalledContractContribution, + InstalledHttpContract, +} from "../../../contracts/external-contract-runtime.ts"; +import type { ProblemDetails } from "./studio/contract.ts"; +import { TECH_LOG_FEATURE_ID } from "../application/tech-log-feature-input.ts"; +import canonicalSource from "./management/canonical-source.json" with { type: "json" }; +import { + envelopeData, + envelopeError, + passthroughInput, +} from "./tech-log-studio-contract-contribution.ts"; +import { TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID } from "../adapters/http/studio-session-credentials.ts"; + +type PathValues = Readonly>; +type QueryEntries = readonly (readonly [string, string])[]; + +const NO_PATH: PathValues = Object.freeze({}); +const NO_QUERY = Object.freeze([]) as QueryEntries; +const PROBLEM = envelopeError(); + +/** Studio 쪽과 같은 판정이다: 4xx 도메인 거절은 적용되지 않았음이 확정, 5xx·네트워크는 불확정. */ +const COMMAND_EFFECT: CommandEffectDescriptor = Object.freeze({ + successEffect: "APPLIED_CONFIRMED" as const, + classifyProblem({ status }: Readonly<{ status: number; problem: unknown }>) { + return status >= 400 && status < 500 ? "NOT_APPLIED" : "MAYBE_APPLIED"; + }, +}); + +/** + * 관리 표면은 Studio 와 같은 세션·CSRF 를 쓴다. 계약이 `sessionCookie` 보안과 `CsrfToken` + * 파라미터를 선언하고 있고, 실제로 같은 백엔드의 같은 필터 체인을 지난다 — 그래서 인증 프로필도 + * 공유한다. 부트스트랩 프로필은 쓰지 않는다: CSRF 토큰을 발급하는 것은 `getStudioSession` + * 하나뿐이고, 이 표면은 그 뒤에만 호출된다. + */ +function readOperation( + operationId: string, + pathTemplate: string, + responseByteLimit: number, + project: (input: never) => Readonly<{ pathValues: PathValues; queryEntries: QueryEntries }> = () => + Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY }), +): InstalledHttpContract { + return Object.freeze({ + contract: Object.freeze({ + operationId, + method: "GET" as const, + pathTemplate, + inputValidator: passthroughInput(`${operationId}Input`), + outputValidator: envelopeData(`${operationId}Output`), + problemValidator: PROBLEM, + acceptedStatuses: Object.freeze([200]), + emptyBodyStatuses: Object.freeze([]), + retrySemantics: "SAFE" as const, + requestBody: "NONE" as const, + responseBody: "REQUIRED_JSON" as const, + commandRecovery: null, + commandEffect: null, + projectRequest(input: never) { + return Object.freeze({ ...project(input), body: null }); + }, + }), + frontend: Object.freeze({ + policyId: `${operationId}_V1`, + requestByteLimit: 0, + responseByteLimit, + totalDeadlineMs: 10_000, + retryBudget: 2 as const, + authProfileId: TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID, + diagnosticsOperation: `techLog.management.${operationId}`, + }), + }) as InstalledHttpContract; +} + +/** + * 쓰기는 `Idempotency-Key` 를 쓰지 않는다 — 계약이 요구하지 않고, 재생 보호는 `expectedVersion` + * 이 맡는다. 그래서 `NOT_IDEMPOTENT` 가 아니라 재시도 예산 0 으로 둔다: 응답을 못 본 재시도가 + * 두 번째 생성을 만들 수 있는 표면이다. + */ +function writeOperation( + operationId: string, + method: "POST" | "PUT" | "DELETE", + pathTemplate: string, + options: Readonly<{ + acceptedStatuses: readonly number[]; + emptyBodyStatuses?: readonly number[]; + requestByteLimit: number; + responseByteLimit: number; + }>, + project: (input: never) => Readonly<{ + pathValues: PathValues; + queryEntries: QueryEntries; + body: unknown; + }>, +): InstalledHttpContract { + return Object.freeze({ + contract: Object.freeze({ + operationId, + method, + pathTemplate, + inputValidator: passthroughInput(`${operationId}Input`), + outputValidator: envelopeData(`${operationId}Output`), + problemValidator: PROBLEM, + acceptedStatuses: Object.freeze([...options.acceptedStatuses]), + emptyBodyStatuses: Object.freeze([...(options.emptyBodyStatuses ?? [])]), + // 생성은 재생 보호가 없으므로 NEVER 다. 수정·삭제는 expectedVersion 이 두 번째 + // 적용을 409 로 막으므로 IDEMPOTENT 로 둘 수 있지만, 세 경우를 한 헬퍼가 만들고 + // 있어 가장 보수적인 값으로 통일한다 — 재시도 예산도 0 이라 실제 차이는 없다. + retrySemantics: "NEVER" as const, + requestBody: "JSON" as const, + responseBody: + (options.emptyBodyStatuses ?? []).length > 0 + ? ("OPTIONAL_JSON" as const) + : ("REQUIRED_JSON" as const), + commandRecovery: null, + commandEffect: COMMAND_EFFECT, + projectRequest(input: never) { + return Object.freeze(project(input)); + }, + }), + frontend: Object.freeze({ + policyId: `${operationId}_V1`, + requestByteLimit: options.requestByteLimit, + responseByteLimit: options.responseByteLimit, + totalDeadlineMs: 15_000, + retryBudget: 0 as const, + authProfileId: TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID, + diagnosticsOperation: `techLog.management.${operationId}`, + }), + // read 쪽과 달리 여기서만 unknown 을 거친다: `emptyBodyStatuses` 유무로 responseBody 가 + // 갈리는 삼항이 union 타입을 만들어, 컴파일러가 리터럴을 대상 타입과 겹친다고 보지 않는다. + }) as unknown as InstalledHttpContract; +} + +const byId = (input: never) => { + const value = input as unknown as Readonly<{ id: string }>; + return Object.freeze({ pathValues: Object.freeze({ id: value.id }), queryEntries: NO_QUERY }); +}; + +const T = "/api/v1/studio/topics"; +const P = "/api/v1/studio/projects"; + +const HTTP_CONTRACTS = Object.freeze([ + readOperation("listStudioTopics", T, 131_072), + writeOperation( + "createTopic", + "POST", + T, + { acceptedStatuses: [201], requestByteLimit: 16_384, responseByteLimit: 16_384 }, + (input: never) => + Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY, body: input }), + ), + writeOperation( + "updateTopic", + "PUT", + `${T}/{id}`, + { acceptedStatuses: [200], requestByteLimit: 16_384, responseByteLimit: 16_384 }, + (input: never) => { + const value = input as unknown as Readonly<{ id: string; body: unknown }>; + return Object.freeze({ + pathValues: Object.freeze({ id: value.id }), + queryEntries: NO_QUERY, + body: value.body, + }); + }, + ), + writeOperation( + "deleteTopic", + "DELETE", + `${T}/{id}`, + { + acceptedStatuses: [204], + emptyBodyStatuses: [204], + requestByteLimit: 1_024, + responseByteLimit: 1_024, + }, + (input: never) => { + const value = input as unknown as Readonly<{ id: string; expectedVersion: number }>; + return Object.freeze({ + pathValues: Object.freeze({ id: value.id }), + queryEntries: NO_QUERY, + body: { expectedVersion: value.expectedVersion }, + }); + }, + ), + readOperation("listStudioProjects", P, 262_144, (input: never) => { + const value = input as unknown as Readonly<{ page?: number; size?: number }> | undefined; + const entries: (readonly [string, string])[] = []; + if (value?.page !== undefined) entries.push(["page", String(value.page)]); + if (value?.size !== undefined) entries.push(["size", String(value.size)]); + return Object.freeze({ pathValues: NO_PATH, queryEntries: Object.freeze(entries) }); + }), + readOperation("getProjectForEdit", `${P}/{id}`, 262_144, byId), + writeOperation( + "createProject", + "POST", + P, + { acceptedStatuses: [201], requestByteLimit: 4_096, responseByteLimit: 8_192 }, + (input: never) => + Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY, body: input }), + ), + writeOperation( + "updateProject", + "PUT", + `${P}/{id}`, + { acceptedStatuses: [200], requestByteLimit: 131_072, responseByteLimit: 262_144 }, + (input: never) => { + const value = input as unknown as Readonly<{ id: string; body: unknown }>; + return Object.freeze({ + pathValues: Object.freeze({ id: value.id }), + queryEntries: NO_QUERY, + body: value.body, + }); + }, + ), + writeOperation( + "deleteProject", + "DELETE", + `${P}/{id}`, + { + acceptedStatuses: [204], + emptyBodyStatuses: [204], + requestByteLimit: 1_024, + responseByteLimit: 1_024, + }, + (input: never) => { + const value = input as unknown as Readonly<{ id: string; expectedVersion: number }>; + return Object.freeze({ + pathValues: Object.freeze({ id: value.id }), + queryEntries: NO_QUERY, + body: { expectedVersion: value.expectedVersion }, + }); + }, + ), +]); + +export const TECH_LOG_MANAGEMENT_OPERATION_IDS = Object.freeze( + HTTP_CONTRACTS.map((entry) => entry.contract.operationId), +); + +export const TECH_LOG_MANAGEMENT_CONTRIBUTION: InstalledContractContribution = Object.freeze({ + contributionId: "tech-log-management-http-v1", + featureId: TECH_LOG_FEATURE_ID, + source: Object.freeze({ + kind: "EXTERNAL_PACKAGE" as const, + package: Object.freeze({ + packageId: canonicalSource.packageId, + version: canonicalSource.version, + digest: canonicalSource.digest as `sha256:${string}`, + runtimeProtocolVersion: 1 as const, + sourceRevision: canonicalSource.sourceRevision, + }), + }), + http: HTTP_CONTRACTS, + events: Object.freeze([]), +}); diff --git a/src/features/tech-log/contracts/tech-log-route-contract.ts b/src/features/tech-log/contracts/tech-log-route-contract.ts index a4b8ec7..6c5292e 100644 --- a/src/features/tech-log/contracts/tech-log-route-contract.ts +++ b/src/features/tech-log/contracts/tech-log-route-contract.ts @@ -48,6 +48,7 @@ const TECH_LOG_ROUTE_SPECS = [ defineSpec({ routeId: "TECH_LOG_STUDIO_PUBLICATIONS", path: "/studio/publications", layoutGroup: "STUDIO", paramsSchema: null, searchSchema: null, title: "게시 기록", navigationLabel: "게시 기록", navigationOrder: 20 }), defineSpec({ routeId: "TECH_LOG_STUDIO_PUBLICATION_PREVIEW", path: "/studio/publications/:publicationEventId/preview", layoutGroup: "STUDIO", paramsSchema: "TechLogPublicationEventIdParams", searchSchema: null, title: "게시 Snapshot", navigationLabel: null, navigationOrder: null }), defineSpec({ routeId: "TECH_LOG_STUDIO_ASSETS", path: "/studio/assets", layoutGroup: "STUDIO", paramsSchema: null, searchSchema: null, title: "Asset", navigationLabel: null, navigationOrder: null }), + defineSpec({ routeId: "TECH_LOG_STUDIO_TAXONOMY", path: "/studio/taxonomy", layoutGroup: "STUDIO", paramsSchema: null, searchSchema: null, title: "주제와 프로젝트", navigationLabel: "주제·프로젝트", navigationOrder: 40 }), defineSpec({ routeId: "TECH_LOG_STUDIO_NOT_FOUND", path: "/studio/*", layoutGroup: "STUDIO", paramsSchema: "TechLogStudioSplat", searchSchema: null, title: "Studio 화면을 찾을 수 없습니다", navigationLabel: null, navigationOrder: null }), defineSpec({ routeId: "NOT_FOUND", path: "*", layoutGroup: "PUBLIC", paramsSchema: "NotFoundSplat", searchSchema: null, title: "페이지를 찾을 수 없습니다.", navigationLabel: null, navigationOrder: null }), ] as const; diff --git a/src/features/tech-log/presentation/public/components/explore-filter-form.tsx b/src/features/tech-log/presentation/public/components/explore-filter-form.tsx index 52fe466..36931c4 100644 --- a/src/features/tech-log/presentation/public/components/explore-filter-form.tsx +++ b/src/features/tech-log/presentation/public/components/explore-filter-form.tsx @@ -56,7 +56,11 @@ export function ExploreFilterForm({ item.title.toLocaleLowerCase("ko-KR") === normalizedProject, )?.slug; const hasActiveFilter = Boolean((showType && kind) || topic || project); - const formKey = [kind, topic, project, showType].join(":"); + // 선택지는 나중에 도착하는데 아래 select 들은 `defaultValue` 를 쓰는 비제어 요소다 — + // 첫 렌더에는 맞출 option 이 없어 값이 비어버린다. 도착 여부를 키에 넣어 그때 폼을 + // 다시 마운트시키면 defaultValue 가 적용된다. 제어 요소로 바꾸지 않는 이유는 이 폼이 + // submit 으로 URL 을 만드는 구조라 값의 주인이 URL 이기 때문이다. + const formKey = [kind, topic, project, showType, view.ready].join(":"); function submit(event: React.FormEvent) { event.preventDefault(); diff --git a/src/features/tech-log/presentation/public/components/search-dialog.tsx b/src/features/tech-log/presentation/public/components/search-dialog.tsx index ee01220..c16439d 100644 --- a/src/features/tech-log/presentation/public/components/search-dialog.tsx +++ b/src/features/tech-log/presentation/public/components/search-dialog.tsx @@ -23,7 +23,7 @@ export function SearchDialog({ // result list with a loading skeleton on every key, which is a worse dialog // than a stale-free local filter. The predicate is the same one the catalog // applies for a non-empty query, so the visible result set is unchanged. - const view = usePublicContent(["tech-log", "search", ""], async (queries) => ({ + const view = usePublicContent(["tech-log", "search", "dialog"], async (queries) => ({ entities: await queries.searchPublicContent(""), })); const results = (view.data?.entities ?? []).filter((entity) => diff --git a/src/features/tech-log/presentation/public/pages/search-page.tsx b/src/features/tech-log/presentation/public/pages/search-page.tsx index cf2bd54..9daa1bb 100644 --- a/src/features/tech-log/presentation/public/pages/search-page.tsx +++ b/src/features/tech-log/presentation/public/pages/search-page.tsx @@ -13,7 +13,11 @@ export function SearchPage() { const navigate = useNavigate(); const { search } = useRouteInput<"TECH_LOG_SEARCH">(); const query = optionalString(search.q)?.trim() ?? ""; - const view = usePublicContent(["tech-log", "search", query], async (queries) => ({ + // 키의 마지막 조각이 화면을 구분한다. 헤더의 검색 다이얼로그도 같은 카탈로그를 읽고 + // 빈 검색어일 때 앞 세 조각이 완전히 겹치는데, 두 화면이 담아 오는 모양이 다르다 + // (여기는 `results`, 다이얼로그는 `entities`). 키가 같으면 react-query 가 한쪽 캐시를 + // 다른 쪽에 돌려주고, 받는 쪽은 없는 필드를 읽다 렌더에서 죽는다. + const view = usePublicContent(["tech-log", "search", "page", query], async (queries) => ({ results: await queries.searchPublicContent(query), })); diff --git a/src/features/tech-log/presentation/studio/components/taxonomy-manager.tsx b/src/features/tech-log/presentation/studio/components/taxonomy-manager.tsx new file mode 100644 index 0000000..884b86b --- /dev/null +++ b/src/features/tech-log/presentation/studio/components/taxonomy-manager.tsx @@ -0,0 +1,276 @@ +import { useCallback, useEffect, useState, type FormEvent } from "react"; + +import type { ProjectIndexItem, TopicEdit } from "../../../contracts/management/contract.ts"; +import { useStudio } from "../use-studio.ts"; + +/** + * 주제와 프로젝트 관리. + * + *

이 화면이 존재하는 이유는 문서 발행이 주제를 요구하는데 주제를 만들 곳이 없었기 때문이다. + * 그래서 범위를 목록·생성·삭제로 끊었다 — 편집(이름 변경, 단계 전환, 공개 범위)은 계약에 있고 + * 백엔드도 구현돼 있으나, 그 화면은 별도 설계가 필요하다. + * + *

새 CSS 를 만들지 않는다. 작업본 목록이 쓰는 클래스만 재사용하므로 이 화면은 Studio 의 + * 나머지와 같은 간격·타이포·색을 그대로 따른다. + */ +export function TaxonomyManager() { + const { managementGateway, setRequestAnnouncement } = useStudio(); + const [topics, setTopics] = useState(null); + const [projects, setProjects] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + const [pending, setPending] = useState(false); + const [generation, setGeneration] = useState(0); + + const [topicName, setTopicName] = useState(""); + const [topicSlug, setTopicSlug] = useState(""); + const [projectTitle, setProjectTitle] = useState(""); + + const reload = useCallback(() => setGeneration((value) => value + 1), []); + + useEffect(() => { + let cancelled = false; + setLoading(true); + setError(""); + void Promise.all([managementGateway.listTopics(), managementGateway.listProjects(0, 50)]).then( + ([topicList, projectPage]) => { + if (cancelled) return; + setTopics(topicList); + setProjects(projectPage.items ?? []); + setLoading(false); + }, + () => { + if (cancelled) return; + setError("주제와 프로젝트를 불러오지 못했습니다."); + setLoading(false); + }, + ); + return () => { + cancelled = true; + }; + }, [managementGateway, generation]); + + /** + * slug 를 비워 두면 이름에서 만든다. 한글 이름이 흔한데 slug 는 ASCII 만 받으므로, 비운 채로 + * 저장하면 서버가 422 로 거절한다 — 사용자가 규칙을 몰라도 되도록 여기서 채운다. + */ + const slugify = (value: string) => + value + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/gu, "-") + .replace(/^-+|-+$/gu, ""); + + const submitTopic = async (event: FormEvent) => { + event.preventDefault(); + if (pending) return; + const name = topicName.trim(); + const slug = slugify(topicSlug || topicName); + if (!name || !slug) { + setError("주제 이름과 slug 를 입력해 주세요. slug 는 영문·숫자·하이픈만 가능합니다."); + return; + } + setPending(true); + setError(""); + try { + await managementGateway.createTopic({ name, slug } as TopicEdit); + setTopicName(""); + setTopicSlug(""); + setRequestAnnouncement(`주제 ${name} 을(를) 만들었습니다.`); + reload(); + } catch { + setError("주제를 만들지 못했습니다. 같은 이름이나 slug 가 이미 있을 수 있습니다."); + } finally { + setPending(false); + } + }; + + const submitProject = async (event: FormEvent) => { + event.preventDefault(); + if (pending) return; + const title = projectTitle.trim(); + if (!title) { + setError("프로젝트 이름을 입력해 주세요."); + return; + } + setPending(true); + setError(""); + try { + await managementGateway.createProject(title); + setProjectTitle(""); + setRequestAnnouncement(`프로젝트 ${title} 을(를) 만들었습니다.`); + reload(); + } catch { + setError("프로젝트를 만들지 못했습니다."); + } finally { + setPending(false); + } + }; + + const removeTopic = async (topic: TopicEdit) => { + if (pending || topic.id === undefined || topic.version === undefined) return; + setPending(true); + setError(""); + try { + await managementGateway.deleteTopic(topic.id, topic.version); + setRequestAnnouncement(`주제 ${topic.name} 을(를) 삭제했습니다.`); + reload(); + } catch { + setError("주제를 삭제하지 못했습니다. 이 주제를 쓰는 기록이 있을 수 있습니다."); + } finally { + setPending(false); + } + }; + + const removeProject = async (project: ProjectIndexItem) => { + if (pending) return; + setPending(true); + setError(""); + try { + await managementGateway.deleteProject(project.id, project.version); + setRequestAnnouncement(`프로젝트 ${project.name} 을(를) 삭제했습니다.`); + reload(); + } catch { + setError("프로젝트를 삭제하지 못했습니다. 연결된 기록이 있을 수 있습니다."); + } finally { + setPending(false); + } + }; + + return ( +

+
+
+

TAXONOMY

+

주제와 프로젝트

+

문서를 게시하려면 주제가 필요합니다. 여기서 만들고 정리합니다.

+
+
+ +
+
+ +
+ setTopicName(event.target.value)} + /> + setTopicSlug(event.target.value)} + /> + +
+
+
+ +
+ setProjectTitle(event.target.value)} + /> + +
+
+
+ + {loading ? ( +

+ 주제와 프로젝트를 불러오는 중입니다. +

+ ) : null} + {error ? ( +

+ {error} +

+ ) : null} + + {!loading && topics ? ( + <> +

{topics.length}개의 주제

+ {topics.length ? ( +
+ {topics.map((topic) => ( +
+

TOPIC

+
+

{topic.name}

+

{topic.slug}

+
+
+
+
상태
+
{topic.status === "ARCHIVED" ? "보관" : "사용 중"}
+
+
+ +
+ ))} +
+ ) : ( +

아직 주제가 없습니다. 위에서 하나 만들어 주세요.

+ )} + + ) : null} + + {!loading && projects ? ( + <> +

{projects.length}개의 프로젝트

+ {projects.length ? ( +
+ {projects.map((project) => ( +
+

PROJECT

+
+

{project.name}

+

{project.currentObjective ?? "목표 미지정"}

+
+
+
+
단계
+
{project.phase}
+
+
+
공개
+
{project.targetVisibility}
+
+
+ +
+ ))} +
+ ) : ( +

아직 프로젝트가 없습니다.

+ )} + + ) : null} +
+ ); +} diff --git a/src/features/tech-log/presentation/studio/pages/taxonomy-page.tsx b/src/features/tech-log/presentation/studio/pages/taxonomy-page.tsx new file mode 100644 index 0000000..8a65f39 --- /dev/null +++ b/src/features/tech-log/presentation/studio/pages/taxonomy-page.tsx @@ -0,0 +1,5 @@ +import { TaxonomyManager } from "../components/taxonomy-manager.tsx"; + +export function TaxonomyPage() { + return ; +} diff --git a/src/features/tech-log/presentation/studio/studio-provider.tsx b/src/features/tech-log/presentation/studio/studio-provider.tsx index 21580a7..36355a8 100644 --- a/src/features/tech-log/presentation/studio/studio-provider.tsx +++ b/src/features/tech-log/presentation/studio/studio-provider.tsx @@ -9,6 +9,7 @@ import { import { isStudioGatewayError } from "../../application/ports/studio-gateway-error.ts"; import type { StudioAssetGateway } from "../../application/ports/studio-asset-gateway.ts"; import type { StudioGateway } from "../../application/ports/studio-gateway.ts"; +import type { ManagementGateway } from "../../application/ports/management-gateway.ts"; import type { ResolvePublishedLabel } from "../../domain/public-render-content.ts"; import type { WorkingCopy, @@ -29,6 +30,7 @@ type StudioProviderProps = Readonly<{ // Optional so test harnesses that only exercise the document gateway keep // working unchanged. `StudioShell` always supplies one in the running app. createAssetGateway?: () => StudioAssetGateway; + createManagementGateway: () => ManagementGateway; resolvePublishedLabel?: ResolvePublishedLabel; now?: () => Date; navigate?: (href: string) => void; @@ -53,6 +55,7 @@ export function StudioProvider({ children, createGateway, createAssetGateway, + createManagementGateway, resolvePublishedLabel = missingPublishedLabel, now = () => new Date("2026-08-14T01:00:00.000Z"), navigate = defaultNavigate, @@ -64,6 +67,7 @@ export function StudioProvider({ const [assetGateway] = useState( () => createAssetGateway?.() ?? null, ); + const [managementGateway] = useState(() => createManagementGateway()); const [editor, setEditor] = useState(null); const [pendingHref, setPendingHref] = useState(null); const [requestAnnouncement, setRequestAnnouncement] = useState(""); @@ -159,6 +163,7 @@ export function StudioProvider({ const value = useMemo( () => ({ gateway, + managementGateway, assetGateway, resolvePublishedLabel, now, @@ -177,6 +182,7 @@ export function StudioProvider({ clearEditor, editor, gateway, + managementGateway, navigateInternal, now, requestAnnouncement, diff --git a/src/features/tech-log/presentation/studio/studio-shell.tsx b/src/features/tech-log/presentation/studio/studio-shell.tsx index 99d49c7..34d6e4d 100644 --- a/src/features/tech-log/presentation/studio/studio-shell.tsx +++ b/src/features/tech-log/presentation/studio/studio-shell.tsx @@ -58,6 +58,10 @@ export function StudioShell({ children }: StudioShellProps) { () => application.features.get(TECH_LOG_FEATURE_ID).createStudioGateway(), [application], ); + const createManagementGateway = useCallback( + () => application.features.get(TECH_LOG_FEATURE_ID).createManagementGateway(), + [application], + ); const createAssetGateway = useCallback( () => application.features.get(TECH_LOG_FEATURE_ID).createStudioAssetGateway(), [application], @@ -92,6 +96,7 @@ export function StudioShell({ children }: StudioShellProps) { key={generation} createGateway={createGateway} createAssetGateway={createAssetGateway} + createManagementGateway={createManagementGateway} resolvePublishedLabel={resolvePublishedLabel} navigate={navigateInternal} > diff --git a/src/features/tech-log/presentation/studio/use-studio.ts b/src/features/tech-log/presentation/studio/use-studio.ts index aea95e4..5be80a4 100644 --- a/src/features/tech-log/presentation/studio/use-studio.ts +++ b/src/features/tech-log/presentation/studio/use-studio.ts @@ -2,6 +2,7 @@ import { createContext, useContext, useMemo } from "react"; import type { StudioAssetGateway } from "../../application/ports/studio-asset-gateway.ts"; import type { StudioGateway } from "../../application/ports/studio-gateway.ts"; +import type { ManagementGateway } from "../../application/ports/management-gateway.ts"; import type { ResolvePublishedLabel } from "../../domain/public-render-content.ts"; import type { WorkingCopy, @@ -23,6 +24,9 @@ export type StudioContextValue = Readonly<{ // `createAssetGateway` prop. `StudioShell` — the real app path — always // supplies one, so production code sees this populated. assetGateway: StudioAssetGateway | null; + // 주제·프로젝트 관리. `assetGateway` 와 같은 이유로 nullable 이 아니다 — 이 표면은 + // MOCK 소스가 없어 항상 HTTP 이고, 없는 경우가 존재하지 않는다. + managementGateway: ManagementGateway; resolvePublishedLabel: ResolvePublishedLabel; now(): Date; editor: StudioEditorState | null; diff --git a/src/features/tech-log/presentation/tech-log-route-runtime.tsx b/src/features/tech-log/presentation/tech-log-route-runtime.tsx index 66133a2..57bd97b 100644 --- a/src/features/tech-log/presentation/tech-log-route-runtime.tsx +++ b/src/features/tech-log/presentation/tech-log-route-runtime.tsx @@ -209,6 +209,13 @@ export const TECH_LOG_ROUTE_RUNTIME = Object.freeze({ "AssetsPage", ), ), + TECH_LOG_STUDIO_TAXONOMY: runtime( + "TECH_LOG_STUDIO_TAXONOMY", + routeModule( + () => import("./studio/pages/taxonomy-page.tsx"), + "TaxonomyPage", + ), + ), TECH_LOG_STUDIO_NOT_FOUND: runtime( "TECH_LOG_STUDIO_NOT_FOUND", routeModule( diff --git a/tests/features/tech-log/asset-picker.test.tsx b/tests/features/tech-log/asset-picker.test.tsx index 71f7e18..cf40d18 100644 --- a/tests/features/tech-log/asset-picker.test.tsx +++ b/tests/features/tech-log/asset-picker.test.tsx @@ -28,6 +28,7 @@ import { MOCK_STUDIO_INSTALL_CONTEXT } from "../../helpers/studio-install-contex import type { StudioAssetGateway } from "../../../src/features/tech-log/application/ports/studio-asset-gateway.ts"; import { StudioGatewayError } from "../../../src/features/tech-log/application/ports/studio-gateway-error.ts"; import type { Asset } from "../../../src/features/tech-log/contracts/studio/contract.ts"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; // jsdom does not implement `` -- same polyfill `studio-save-navigation.test.tsx` // and `studio-decision-authoring.test.tsx` already use for the other Studio dialogs. @@ -433,7 +434,8 @@ test("inserts the directive at the saved cursor position and the live preview re render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , @@ -538,7 +540,8 @@ test("an asset uploaded through the mock composition previews live and passes mo render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , @@ -1259,7 +1262,8 @@ function caseDraft(bodyMarkdown: string) { function renderInstantPreview(bodyMarkdown: string, assets: readonly Asset[]) { render( - createMockStudioGateway()}> + createMockStudioGateway()}> , @@ -1479,7 +1483,8 @@ test("an asset uploaded with alt text inserts that alt and the document validate render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , @@ -1524,7 +1529,8 @@ test("a decorative upload inserts an empty alt and the document is still publish render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , @@ -1797,7 +1803,8 @@ test("a Picker search never drops an already-inserted asset out of Instant Previ render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , diff --git a/tests/features/tech-log/feature-input.test.ts b/tests/features/tech-log/feature-input.test.ts index d379aae..1d4bb64 100644 --- a/tests/features/tech-log/feature-input.test.ts +++ b/tests/features/tech-log/feature-input.test.ts @@ -16,7 +16,10 @@ type Assert = Condition; type TechLogFeatureInputExposesNoMissingOrAdditionalKeys = Assert< Equal< keyof ApplicationFeatureInputs["tech-log"], - "publicContent" | "createStudioGateway" | "createStudioAssetGateway" + | "publicContent" + | "createStudioGateway" + | "createStudioAssetGateway" + | "createManagementGateway" > >; type TechLogFeatureInputRegistryValueMatchesFeatureContract = Assert< diff --git a/tests/features/tech-log/navigation-derivation.test.ts b/tests/features/tech-log/navigation-derivation.test.ts index a1dab08..bc1a2e0 100644 --- a/tests/features/tech-log/navigation-derivation.test.ts +++ b/tests/features/tech-log/navigation-derivation.test.ts @@ -28,6 +28,7 @@ describe("TechLog navigation derivation", () => { ["작업본", "/studio/documents"], ["게시 기록", "/studio/publications"], ["새 문서", "/studio/documents/new"], + ["주제·프로젝트", "/studio/taxonomy"], ]); }); diff --git a/tests/features/tech-log/public-discovery-screens.test.tsx b/tests/features/tech-log/public-discovery-screens.test.tsx index d757185..49506c7 100644 --- a/tests/features/tech-log/public-discovery-screens.test.tsx +++ b/tests/features/tech-log/public-discovery-screens.test.tsx @@ -34,6 +34,7 @@ import { ApplicationProvider } from "../../../src/presentation/providers/applica import { createGroupedRouteObjects } from "../../../src/presentation/routes/app-router.tsx"; import { PLATFORM_ROUTE_CODECS } from "../../../src/presentation/routes/platform-route-codecs.ts"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; const routeCodecs = Object.freeze({ ...PLATFORM_ROUTE_CODECS, @@ -54,7 +55,7 @@ type DiscoveryRenderOptions = Readonly<{ application?: ReturnType; }>; -function renderDiscoveryRoute( +async function renderDiscoveryRoute( routeId: RouteId, initialEntry: string, options: DiscoveryRenderOptions = {}, @@ -93,6 +94,7 @@ function renderDiscoveryRoute( ); const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const view = render( + renderWithQueryProviders( ( > , + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + // `main` 을 기다리지 않는다 — 이 파일에는 의도적으로 렌더에 실패하는 라우트도 있고, + // 그때는 main 이 아예 없다. 로딩 표면이 사라지는 것이 두 경우 모두에 맞는 기준이다. + await waitFor(() => + expect(document.querySelector(".state-surface--loading")).toBeNull(), ); return { ...view, router } satisfies ReturnType & { router: RouterProviderProps["router"]; @@ -122,7 +131,7 @@ afterEach(() => { describe("TechLog home discovery", () => { it("renders the exact identity, focus, latest ordering, and explore choices", async () => { - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_HOME", "/?focus=invalid&focus=question", ); @@ -174,7 +183,7 @@ describe("TechLog home discovery", () => { it("moves linked tabs with arrows, Home, and End while synchronizing the URL", async () => { const user = userEvent.setup(); - const { router } = renderDiscoveryRoute("TECH_LOG_HOME", "/?focus=question"); + const { router } = await renderDiscoveryRoute("TECH_LOG_HOME", "/?focus=question"); const question = screen.getByRole("tab", { name: "열린 질문" }); question.focus(); @@ -195,7 +204,7 @@ describe("TechLog home discovery", () => { }); it("treats external focus URL changes as authoritative without stealing focus", async () => { - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_HOME", "/?focus=current&state=latest-empty#latest", ); @@ -250,14 +259,14 @@ describe("TechLog home discovery", () => { expect(screen.getAllByRole("tabpanel")).toHaveLength(1); }); - it("preserves the source home empty and error states", () => { - const emptyView = renderDiscoveryRoute("TECH_LOG_HOME", "/?state=latest-empty"); + it("preserves the source home empty and error states", async () => { + const emptyView = await renderDiscoveryRoute("TECH_LOG_HOME", "/?state=latest-empty"); expect(screen.getByText("아직 공개된 기록이 없습니다.")).toHaveClass( "latest-state--empty", ); emptyView.unmount(); - const latestErrorView = renderDiscoveryRoute( + const latestErrorView = await renderDiscoveryRoute( "TECH_LOG_HOME", "/?state=latest-error", ); @@ -270,14 +279,14 @@ describe("TechLog home discovery", () => { ); latestErrorView.unmount(); - const errorView = renderDiscoveryRoute("TECH_LOG_HOME", "/?state=site-error"); + const errorView = await renderDiscoveryRoute("TECH_LOG_HOME", "/?state=site-error"); expect( screen.getByRole("heading", { name: "페이지를 불러오지 못했습니다." }), ).toBeVisible(); expect(screen.getByText("PREVIEW-HOME-500")).toBeVisible(); errorView.unmount(); - renderDiscoveryRoute("TECH_LOG_HOME", "/?state=focus-empty"); + await renderDiscoveryRoute("TECH_LOG_HOME", "/?state=focus-empty"); expect(screen.queryByText("지금 집중하는 것")).not.toBeInTheDocument(); }); }); @@ -285,7 +294,7 @@ describe("TechLog home discovery", () => { describe("TechLog explore discovery", () => { it("filters by kind, topic, and project and keeps source result structure", async () => { const user = userEvent.setup(); - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_EXPLORE", "/explore?type=CASE&topic=JPA&project=backend-skeleton", ); @@ -294,9 +303,12 @@ describe("TechLog explore discovery", () => { expect( screen.getByText("유형과 기술 주제, 프로젝트를 조합해 공개 기록을 찾습니다."), ).toBeVisible(); - expect(screen.getByLabelText("유형")).toHaveValue("CASE"); - expect(screen.getByLabelText("주제")).toHaveValue("JPA"); - expect(screen.getByLabelText("프로젝트")).toHaveValue("backend-skeleton"); + // 필터의 선택지는 카탈로그가 도착한 뒤 채워지고, select 의 값도 그때 설정된다. + await waitFor(() => { + expect(screen.getByLabelText("유형")).toHaveValue("CASE"); + expect(screen.getByLabelText("주제")).toHaveValue("JPA"); + expect(screen.getByLabelText("프로젝트")).toHaveValue("backend-skeleton"); + }); expect(screen.getByText("1개의 공개 기록")).toBeVisible(); expect( screen.getByRole("link", { name: /컬렉션 Fetch Join과 페이징은 왜 충돌하는가/ }), @@ -337,8 +349,8 @@ describe("TechLog explore discovery", () => { ]); }); - it("renders the distinct no-result state for an unmatched query filter", () => { - renderDiscoveryRoute("TECH_LOG_EXPLORE", "/explore?topic=missing"); + it("renders the distinct no-result state for an unmatched query filter", async () => { + await renderDiscoveryRoute("TECH_LOG_EXPLORE", "/explore?topic=missing"); expect(screen.getByText("0개의 공개 기록")).toBeVisible(); expect(screen.getByText("조건에 맞는 공개 기록이 없습니다.")).toHaveClass( @@ -351,8 +363,8 @@ describe("TechLog explore discovery", () => { ); }); - it("keeps kind-specific copy, filtering, count, ordering, and back navigation", () => { - renderDiscoveryRoute( + it("keeps kind-specific copy, filtering, count, ordering, and back navigation", async () => { + await renderDiscoveryRoute( "TECH_LOG_EXPLORE_KIND", "/explore/questions?topic=JPA&project=backend-skeleton", ); @@ -374,7 +386,7 @@ describe("TechLog explore discovery", () => { }); it("keeps the unreachable unknown-kind client fallback accessible", async () => { - const { router, container } = renderDiscoveryRoute( + const { router, container } = await renderDiscoveryRoute( "TECH_LOG_EXPLORE_KIND", "/explore/unknown", ); @@ -425,7 +437,7 @@ describe("TechLog explore discovery", () => { }, featureInputs: { "tech-log": techLog }, }); - const { router, container } = renderDiscoveryRoute( + const { router, container } = await renderDiscoveryRoute( "TECH_LOG_EXPLORE_KIND", "/explore/unknown", { NotFoundComponent: LazyNotFound, application }, @@ -473,7 +485,7 @@ describe("TechLog explore discovery", () => { describe("TechLog search discovery", () => { it("normalizes repeated queries, preserves result ordering, and navigates canonically", async () => { const user = userEvent.setup(); - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_SEARCH", "/search?q=%20JPA%20&q=Redis&unknown=drop", ); @@ -503,8 +515,8 @@ describe("TechLog search discovery", () => { it("submits from the keyboard and synchronizes the searchbox with URL changes", async () => { const user = userEvent.setup(); - const { router } = renderDiscoveryRoute("TECH_LOG_SEARCH", "/search"); - const input = screen.getByRole("searchbox", { name: "검색어" }); + const { router } = await renderDiscoveryRoute("TECH_LOG_SEARCH", "/search"); + const input = await screen.findByRole("searchbox", { name: "검색어" }); await user.type(input, "Redis{Enter}"); await waitFor(() => { @@ -523,7 +535,7 @@ describe("TechLog search discovery", () => { }); it("normalizes an explicitly empty first query and renders all canonical results", async () => { - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_SEARCH", "/search?q=%20%20&q=JPA", ); @@ -531,13 +543,13 @@ describe("TechLog search discovery", () => { await waitFor(() => { expect(router.state.location.search).toBe(""); }); - expect(screen.getByRole("heading", { name: "전체 검색 결과" })).toBeVisible(); + expect(await screen.findByRole("heading", { name: "전체 검색 결과" })).toBeVisible(); expect(screen.getByText("9개의 검색 결과")).toBeVisible(); expect(screen.getByRole("searchbox", { name: "검색어" })).toHaveValue(""); }); - it("renders the exact zero-result state without a result list", () => { - renderDiscoveryRoute("TECH_LOG_SEARCH", "/search?q=존재하지않음"); + it("renders the exact zero-result state without a result list", async () => { + await renderDiscoveryRoute("TECH_LOG_SEARCH", "/search?q=존재하지않음"); expect(screen.getByText("0개의 검색 결과")).toBeVisible(); expect(screen.getByText("일치하는 공개 기록이 없습니다.")).toHaveClass( @@ -548,7 +560,7 @@ describe("TechLog search discovery", () => { }); describe("home focus domain", () => { - it("normalizes selection and wraps all supported keyboard movements", () => { + it("normalizes selection and wraps all supported keyboard movements", async () => { const keys = ["current", "question", "decision"] as const; expect(normalizeFocus(undefined, keys)).toBe("current"); diff --git a/tests/features/tech-log/public-document-screens.test.tsx b/tests/features/tech-log/public-document-screens.test.tsx index eeebdeb..3f01a11 100644 --- a/tests/features/tech-log/public-document-screens.test.tsx +++ b/tests/features/tech-log/public-document-screens.test.tsx @@ -26,6 +26,7 @@ import { ApplicationProvider } from "../../../src/presentation/providers/applica import { createGroupedRouteObjects } from "../../../src/presentation/routes/app-router.tsx"; import { PLATFORM_ROUTE_CODECS } from "../../../src/presentation/routes/platform-route-codecs.ts"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; const routeCodecs = Object.freeze({ ...PLATFORM_ROUTE_CODECS, @@ -63,7 +64,7 @@ afterEach(() => { vi.unstubAllGlobals(); }); -function renderDocumentRoute(routeId: DocumentRouteId, initialEntry: string) { +async function renderDocumentRoute(routeId: DocumentRouteId, initialEntry: string) { const definition = TECH_LOG_ROUTE_REGISTRY[routeId]; const runtime = TECH_LOG_ROUTE_RUNTIME_CONTRACT[routeId]; const Component = routeComponents[routeId]; @@ -95,6 +96,7 @@ function renderDocumentRoute(routeId: DocumentRouteId, initialEntry: string) { ); const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const view = render( + renderWithQueryProviders( , - ); + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + await screen.findByRole("main"); return { ...view, router }; } @@ -195,8 +200,8 @@ describe("TechLog canonical Public documents", () => { }, ])( "renders $path with exact metadata, evidence, and ordered relations", - ({ routeId, path, title, kind, topic, project, published, evidence, relations }) => { - const { container } = renderDocumentRoute(routeId, path); + async ({ routeId, path, title, kind, topic, project, published, evidence, relations }) => { + const { container } = await renderDocumentRoute(routeId, path); const main = screen.getByRole("main"); expect(within(main).getByRole("heading", { level: 1, name: title })).toBeVisible(); @@ -222,8 +227,8 @@ describe("TechLog canonical Public documents", () => { }, ); - it("preserves the specialized Fetch Join layout, TOC, rendered blocks, anchors, and evidence media", () => { - const { container } = renderDocumentRoute( + it("preserves the specialized Fetch Join layout, TOC, rendered blocks, anchors, and evidence media", async () => { + const { container } = await renderDocumentRoute( "TECH_LOG_CASE", "/cases/collection-fetch-join-pagination", ); @@ -253,8 +258,8 @@ describe("TechLog canonical Public documents", () => { expect(image).toHaveAttribute("loading", "lazy"); }); - it("uses the generic Case markup and omits source relations for the canonical empty state", () => { - const generic = renderDocumentRoute( + it("uses the generic Case markup and omits source relations for the canonical empty state", async () => { + const generic = await renderDocumentRoute( "TECH_LOG_CASE", "/cases/redis-adapter-ttl-boundary", ); @@ -264,7 +269,7 @@ describe("TechLog canonical Public documents", () => { ); generic.unmount(); - const empty = renderDocumentRoute( + const empty = await renderDocumentRoute( "TECH_LOG_CASE", "/cases/collection-fetch-join-pagination?state=relations-empty", ); @@ -273,8 +278,8 @@ describe("TechLog canonical Public documents", () => { expect(screen.queryByRole("heading", { name: "이 기록의 연결" })).not.toBeInTheDocument(); }); - it("preserves Reference rule order and resolved Question evidence and empty copy", () => { - const reference = renderDocumentRoute( + it("preserves Reference rule order and resolved Question evidence and empty copy", async () => { + const reference = await renderDocumentRoute( "TECH_LOG_REFERENCE", "/references/state-and-nonce-boundary", ); @@ -290,7 +295,7 @@ describe("TechLog canonical Public documents", () => { expect(screen.getByText("마지막 검증 2026.08.09")).toBeVisible(); reference.unmount(); - renderDocumentRoute( + await renderDocumentRoute( "TECH_LOG_QUESTION", "/questions/collection-fetch-join-with-pagination", ); @@ -311,16 +316,16 @@ describe("TechLog topics and Public not-found routing", () => { ["jpa", "JPA", "목록 조회, 연관 로딩과 페이지 경계를 함께 검증한 기록입니다.", "3개의 관련 기록"], ["authentication", "Authentication", "브라우저와 Edge, Resource Server 사이의 인증 책임과 신뢰 경계를 검증한 기록입니다.", "2개의 관련 기록"], ["redis", "Redis", "애플리케이션 정책과 Redis 저장 명령의 책임 경계를 검증한 기록입니다.", "1개의 관련 기록"], - ])("aggregates /topics/%s with exact copy and count", (slug, title, description, count) => { - renderDocumentRoute("TECH_LOG_TOPIC", `/topics/${slug}`); + ])("aggregates /topics/%s with exact copy and count", async (slug, title, description, count) => { + await renderDocumentRoute("TECH_LOG_TOPIC", `/topics/${slug}`); expect(screen.getByRole("heading", { level: 1, name: title })).toBeVisible(); expect(screen.getByText(description)).toBeVisible(); expect(screen.getByText(count)).toBeVisible(); }); - it("keeps JPA topic records in canonical published order", () => { - const { container } = renderDocumentRoute("TECH_LOG_TOPIC", "/topics/jpa"); + it("keeps JPA topic records in canonical published order", async () => { + const { container } = await renderDocumentRoute("TECH_LOG_TOPIC", "/topics/jpa"); expect( Array.from(container.querySelectorAll(".public-record-list > li > a"), (link) => @@ -339,7 +344,7 @@ describe("TechLog topics and Public not-found routing", () => { ["TECH_LOG_QUESTION" as const, "/questions/not-registered"], ["TECH_LOG_TOPIC" as const, "/topics/not-registered"], ])("keeps the unreachable client fallback accessible for %s", async (routeId, path) => { - const { container, router } = renderDocumentRoute(routeId, path); + const { container, router } = await renderDocumentRoute(routeId, path); expect(router.state.location.pathname).toBe(path); expect( diff --git a/tests/features/tech-log/public-index-screens.test.tsx b/tests/features/tech-log/public-index-screens.test.tsx index 835cc24..2d07fc7 100644 --- a/tests/features/tech-log/public-index-screens.test.tsx +++ b/tests/features/tech-log/public-index-screens.test.tsx @@ -31,6 +31,7 @@ import { ApplicationProvider } from "../../../src/presentation/providers/applica import { createGroupedRouteObjects } from "../../../src/presentation/routes/app-router.tsx"; import { PLATFORM_ROUTE_CODECS } from "../../../src/presentation/routes/platform-route-codecs.ts"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; const routeCodecs = Object.freeze({ ...PLATFORM_ROUTE_CODECS, @@ -52,7 +53,7 @@ const routeComponents = { type PublicIndexRouteId = keyof typeof routeComponents; -function renderPublicRoute(routeId: PublicIndexRouteId, initialEntry: string) { +async function renderPublicRoute(routeId: PublicIndexRouteId, initialEntry: string) { const routeIds = routeId === "NOT_FOUND" ? [routeId] : [routeId, "NOT_FOUND" as const]; const registry = Object.fromEntries( routeIds.map((id) => [id, TECH_LOG_ROUTE_REGISTRY[id]]), @@ -85,6 +86,7 @@ function renderPublicRoute(routeId: PublicIndexRouteId, initialEntry: string) { ); const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const view = render( + renderWithQueryProviders( , - ); + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + await screen.findByRole("main"); return { ...view, router }; } @@ -101,8 +106,8 @@ function projectNavigation() { } describe("TechLog project screens", () => { - it("renders the exact ordered project index and project links", () => { - const { container } = renderPublicRoute("TECH_LOG_PROJECTS", "/projects"); + it("renders the exact ordered project index and project links", async () => { + const { container } = await renderPublicRoute("TECH_LOG_PROJECTS", "/projects"); const main = screen.getByRole("main"); expect(within(main).getByRole("heading", { level: 1, name: "프로젝트" })).toBeVisible(); @@ -145,8 +150,8 @@ describe("TechLog project screens", () => { stats: ["2공개 기록", "1설계 결정", "2활동 기록"], topics: ["Authentication", "OAuth 2.0", "OIDC", "Keycloak"], }, - ])("renders $path overview with source counts and topics", ({ path, title, current, thesis, stats, topics }) => { - const { container } = renderPublicRoute("TECH_LOG_PROJECT", path); + ])("renders $path overview with source counts and topics", async ({ path, title, current, thesis, stats, topics }) => { + const { container } = await renderPublicRoute("TECH_LOG_PROJECT", path); const main = screen.getByRole("main"); expect(within(main).getByRole("heading", { level: 1, name: title })).toBeVisible(); @@ -167,8 +172,8 @@ describe("TechLog project screens", () => { ["TECH_LOG_PROJECT_RECORDS" as const, "/projects/backend-skeleton/records", "기록"], ["TECH_LOG_PROJECT_DECISIONS" as const, "/projects/backend-skeleton/decisions", "결정"], ["TECH_LOG_PROJECT_ACTIVITY" as const, "/projects/backend-skeleton/activity", "활동"], - ])("derives the active project tab from %s location", (routeId, path, activeLabel) => { - renderPublicRoute(routeId, path); + ])("derives the active project tab from %s location", async (routeId, path, activeLabel) => { + await renderPublicRoute(routeId, path); expect( within(projectNavigation()) @@ -199,8 +204,8 @@ describe("TechLog project screens", () => { "/questions/validate-edge-token-again", ], }, - ])("keeps $slug records filtered and published in source order", ({ slug, expected }) => { - const { container } = renderPublicRoute( + ])("keeps $slug records filtered and published in source order", async ({ slug, expected }) => { + const { container } = await renderPublicRoute( "TECH_LOG_PROJECT_RECORDS", `/projects/${slug}/records`, ); @@ -213,8 +218,8 @@ describe("TechLog project screens", () => { expect(screen.getByText(`${expected.length}개의 공개 기록`)).toBeVisible(); }); - it("keeps project decisions ordered with stable IDs and evidence links", () => { - const { container } = renderPublicRoute( + it("keeps project decisions ordered with stable IDs and evidence links", async () => { + const { container } = await renderPublicRoute( "TECH_LOG_PROJECT_DECISIONS", "/projects/backend-skeleton/decisions", ); @@ -240,8 +245,8 @@ describe("TechLog project screens", () => { ]); }); - it("keeps project activity ordered and preserves self-fragment and record links", () => { - const { container } = renderPublicRoute( + it("keeps project activity ordered and preserves self-fragment and record links", async () => { + const { container } = await renderPublicRoute( "TECH_LOG_PROJECT_ACTIVITY", "/projects/backend-skeleton/activity", ); @@ -273,8 +278,8 @@ describe("TechLog project screens", () => { }); describe("TechLog release and profile screens", () => { - it("renders the ordered release index and complete version link", () => { - const { container } = renderPublicRoute("TECH_LOG_RELEASES", "/releases"); + it("renders the ordered release index and complete version link", async () => { + const { container } = await renderPublicRoute("TECH_LOG_RELEASES", "/releases"); expect(screen.getByRole("heading", { level: 1, name: "변경 기록" })).toBeVisible(); expect( @@ -297,8 +302,8 @@ describe("TechLog release and profile screens", () => { ]); }); - it("renders exact release sections and ordered cross-links", () => { - const { container } = renderPublicRoute("TECH_LOG_RELEASE", "/releases/0.1.0"); + it("renders exact release sections and ordered cross-links", async () => { + const { container } = await renderPublicRoute("TECH_LOG_RELEASE", "/releases/0.1.0"); const main = screen.getByRole("main"); expect( @@ -325,8 +330,8 @@ describe("TechLog release and profile screens", () => { ).toEqual(["/", "/explore", "/cases/collection-fetch-join-pagination"]); }); - it("renders the grounded profile, principles, project links, and topics", () => { - const { container } = renderPublicRoute("TECH_LOG_PROFILE", "/profile"); + it("renders the grounded profile, principles, project links, and topics", async () => { + const { container } = await renderPublicRoute("TECH_LOG_PROFILE", "/profile"); const main = screen.getByRole("main"); expect(within(main).getByRole("heading", { level: 1, name: "동현" })).toBeVisible(); @@ -362,7 +367,7 @@ describe("TechLog Public not-found runtime", () => { ["TECH_LOG_RELEASE" as const, "/releases/9.9.9"], ["TECH_LOG_CASE" as const, "/cases/not-registered"], ])("keeps the client-only fallback accessible for %s", async (routeId, path) => { - const { container, router } = renderPublicRoute(routeId, path); + const { container, router } = await renderPublicRoute(routeId, path); expect(router.state.location.pathname).toBe(path); expect( @@ -374,8 +379,8 @@ describe("TechLog Public not-found runtime", () => { expect(container.querySelector(".site-frame")).not.toBeNull(); }); - it("keeps the client-only Public catch-all accessible", () => { - const { container, router } = renderPublicRoute( + it("keeps the client-only Public catch-all accessible", async () => { + const { container, router } = await renderPublicRoute( "NOT_FOUND", "/definitely-not-a-product-route", ); diff --git a/tests/features/tech-log/public-shell.test.tsx b/tests/features/tech-log/public-shell.test.tsx index 2dab991..fef1ce7 100644 --- a/tests/features/tech-log/public-shell.test.tsx +++ b/tests/features/tech-log/public-shell.test.tsx @@ -11,6 +11,7 @@ import { FatalErrorState } from "../../../src/features/tech-log/presentation/pub import { PublicShell } from "../../../src/features/tech-log/presentation/public/public-shell.tsx"; import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; const originalShowModal = HTMLDialogElement.prototype.showModal; const originalClose = HTMLDialogElement.prototype.close; @@ -47,7 +48,7 @@ afterEach(() => { }); }); -function renderShell(initialEntry = "/projects") { +async function renderShell(initialEntry = "/projects") { const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const router = createMemoryRouter( [ @@ -65,6 +66,7 @@ function renderShell(initialEntry = "/projects") { { initialEntries: [initialEntry] }, ); const view = render( + renderWithQueryProviders( , - ); + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + await screen.findByRole("main"); return { ...view, router }; } describe("TechLog Public shell", () => { - it("preserves source landmark order, navigation copy, current state, and footer", () => { - const view = renderShell(); + it("preserves source landmark order, navigation copy, current state, and footer", async () => { + const view = await renderShell(); const frame = view.container.querySelector(".site-frame"); expect( @@ -127,7 +132,7 @@ describe("TechLog Public shell", () => { it("uses the canonical root route for brand navigation", async () => { const user = userEvent.setup(); - const { router } = renderShell("/projects/backend-skeleton"); + const { router } = await renderShell("/projects/backend-skeleton"); await user.click(screen.getByRole("link", { name: "TechLog 홈" })); @@ -136,7 +141,7 @@ describe("TechLog Public shell", () => { it("opens search by click, closes on Escape, and restores trigger focus", async () => { const user = userEvent.setup(); - renderShell(); + await renderShell(); const trigger = screen.getByRole("button", { name: "TechLog 검색 열기" }); await user.click(trigger); @@ -155,7 +160,7 @@ describe("TechLog Public shell", () => { it("opens the native search trigger from the keyboard", async () => { const user = userEvent.setup(); - renderShell(); + await renderShell(); const trigger = screen.getByRole("button", { name: "TechLog 검색 열기" }); trigger.focus(); @@ -169,7 +174,7 @@ describe("TechLog Public shell", () => { it("returns source-equivalent results and navigates to their canonical route", async () => { const user = userEvent.setup(); - const { router } = renderShell(); + const { router } = await renderShell(); await user.click( screen.getByRole("button", { name: "TechLog 검색 열기" }), ); @@ -191,7 +196,7 @@ describe("TechLog Public shell", () => { it("navigates the full-search action with its canonical query intact", async () => { const user = userEvent.setup(); - const { router } = renderShell(); + const { router } = await renderShell(); await user.click( screen.getByRole("button", { name: "TechLog 검색 열기" }), ); @@ -210,7 +215,7 @@ describe("TechLog Public shell", () => { it("uses native mobile disclosure activation and closes it after selection", async () => { const user = userEvent.setup(); - const view = renderShell(); + const view = await renderShell(); const details = view.container.querySelector( "details.mobile-nav", ); @@ -236,7 +241,7 @@ describe("TechLog Public shell", () => { it("closes a natively opened mobile disclosure before opening one search dialog", async () => { const user = userEvent.setup(); - const view = renderShell(); + const view = await renderShell(); const details = view.container.querySelector( "details.mobile-nav", ); diff --git a/tests/features/tech-log/route-contract.test.ts b/tests/features/tech-log/route-contract.test.ts index 47f3237..e471e64 100644 --- a/tests/features/tech-log/route-contract.test.ts +++ b/tests/features/tech-log/route-contract.test.ts @@ -38,6 +38,7 @@ const expectedRoutes = [ ["TECH_LOG_STUDIO_PUBLICATIONS", "/studio/publications", "STUDIO", null, null], ["TECH_LOG_STUDIO_PUBLICATION_PREVIEW", "/studio/publications/:publicationEventId/preview", "STUDIO", "TechLogPublicationEventIdParams", null], ["TECH_LOG_STUDIO_ASSETS", "/studio/assets", "STUDIO", null, null], + ["TECH_LOG_STUDIO_TAXONOMY", "/studio/taxonomy", "STUDIO", null, null], ["TECH_LOG_STUDIO_NOT_FOUND", "/studio/*", "STUDIO", "TechLogStudioSplat", null], ["NOT_FOUND", "*", "PUBLIC", "NotFoundSplat", null], ] as const; @@ -69,6 +70,7 @@ const expectedTitles = { TECH_LOG_STUDIO_PUBLICATIONS: "게시 기록", TECH_LOG_STUDIO_PUBLICATION_PREVIEW: "게시 Snapshot", TECH_LOG_STUDIO_ASSETS: "Asset", + TECH_LOG_STUDIO_TAXONOMY: "주제와 프로젝트", TECH_LOG_STUDIO_NOT_FOUND: "Studio 화면을 찾을 수 없습니다", NOT_FOUND: "페이지를 찾을 수 없습니다.", } as const; @@ -142,12 +144,13 @@ describe("TechLog route boundary contract", () => { ["TECH_LOG_STUDIO_DOCUMENTS", "STUDIO", "작업본", 10], ["TECH_LOG_STUDIO_DOCUMENT_NEW", "STUDIO", "새 문서", 30], ["TECH_LOG_STUDIO_PUBLICATIONS", "STUDIO", "게시 기록", 20], + ["TECH_LOG_STUDIO_TAXONOMY", "STUDIO", "주제·프로젝트", 40], ]); for (const locale of ["ko-KR", "en-US"] as const) { const catalog: Readonly> = TECH_LOG_MESSAGE_CATALOGS[locale]; - expect(Object.keys(catalog)).toHaveLength(56); + expect(Object.keys(catalog)).toHaveLength(58); for (const [routeId, title] of Object.entries(expectedTitles)) { expect(catalog[`route.${routeId}.title`]).toBe(title); expect(catalog[`route.${routeId}.navigation`]).toBe(title); diff --git a/tests/features/tech-log/runtime-composition.test.ts b/tests/features/tech-log/runtime-composition.test.ts index 039441a..61d1b89 100644 --- a/tests/features/tech-log/runtime-composition.test.ts +++ b/tests/features/tech-log/runtime-composition.test.ts @@ -60,6 +60,7 @@ test("installs TechLog beside the retained reference feature through application assert.deepEqual(Object.keys(installed), ["reference-feature", "tech-log"]); assert.deepEqual(Object.keys(installed["tech-log"]).sort(), [ + "createManagementGateway", "createStudioAssetGateway", "createStudioGateway", "publicContent", diff --git a/tests/features/tech-log/studio-decision-authoring.test.tsx b/tests/features/tech-log/studio-decision-authoring.test.tsx index cfe1364..9484ad6 100644 --- a/tests/features/tech-log/studio-decision-authoring.test.tsx +++ b/tests/features/tech-log/studio-decision-authoring.test.tsx @@ -14,6 +14,7 @@ import type { WorkingCopyInput } from "../../../src/features/tech-log/contracts/ import { DocumentEditorScreen } from "../../../src/features/tech-log/presentation/studio/components/document-editor-screen.tsx"; import { NewDocumentForm } from "../../../src/features/tech-log/presentation/studio/components/new-document-form.tsx"; import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; type DecisionInput = Extract; @@ -54,6 +55,7 @@ function RouterStudioProvider({ const navigate = useNavigate(); return ( gateway} navigate={(href) => { void navigate(href); }} > @@ -140,7 +142,8 @@ describe("TechLog Studio project decision authoring", () => { }); render( - gateway}> + gateway}> , diff --git a/tests/features/tech-log/studio-editor-smoke.test.tsx b/tests/features/tech-log/studio-editor-smoke.test.tsx index dc55ebd..1fe1806 100644 --- a/tests/features/tech-log/studio-editor-smoke.test.tsx +++ b/tests/features/tech-log/studio-editor-smoke.test.tsx @@ -12,6 +12,7 @@ import { FIXTURE_IDS } from "../../../src/features/tech-log/adapters/mock/fixtur import type { StudioGateway } from "../../../src/features/tech-log/application/ports/studio-gateway.ts"; import { DocumentEditorScreen } from "../../../src/features/tech-log/presentation/studio/components/document-editor-screen.tsx"; import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; afterEach(() => vi.restoreAllMocks()); @@ -26,7 +27,8 @@ function renderEditor( .input.createStudioAssetGateway(); return render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , diff --git a/tests/features/tech-log/studio-publication-flow.test.tsx b/tests/features/tech-log/studio-publication-flow.test.tsx index 630685e..4d4f8fa 100644 --- a/tests/features/tech-log/studio-publication-flow.test.tsx +++ b/tests/features/tech-log/studio-publication-flow.test.tsx @@ -24,6 +24,7 @@ import { PublicationEventPreviewScreen } from "../../../src/features/tech-log/pr import { PublicationList } from "../../../src/features/tech-log/presentation/studio/components/publication-list.tsx"; import { PublishScreen } from "../../../src/features/tech-log/presentation/studio/components/publish-screen.tsx"; import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; const originalShowModal = HTMLDialogElement.prototype.showModal; const originalClose = HTMLDialogElement.prototype.close; @@ -85,7 +86,8 @@ function renderInStudio( ) { return render( - gateway} navigate={navigate}> + gateway} navigate={navigate}> {node} , diff --git a/tests/features/tech-log/studio-save-navigation.test.tsx b/tests/features/tech-log/studio-save-navigation.test.tsx index 602141c..919fa28 100644 --- a/tests/features/tech-log/studio-save-navigation.test.tsx +++ b/tests/features/tech-log/studio-save-navigation.test.tsx @@ -23,6 +23,7 @@ import { useStudio, useStudioEditorSession, } from "../../../src/features/tech-log/presentation/studio/use-studio.ts"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; const originalShowModal = HTMLDialogElement.prototype.showModal; const originalClose = HTMLDialogElement.prototype.close; @@ -86,7 +87,8 @@ function renderEditor(documentId: string, gateway: StudioGateway) { .input.createStudioAssetGateway(); return render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> @@ -214,7 +216,8 @@ describe("TechLog Studio dirty navigation", () => { const saved = await getSavedDocument(gateway); render( - gateway} navigate={(href) => destinations.push(href)}> + gateway} navigate={(href) => destinations.push(href)}> , @@ -251,7 +254,8 @@ describe("TechLog Studio dirty navigation", () => { const saved = await getSavedDocument(gateway); render( - gateway} navigate={(href) => destinations.push(href)}> + gateway} navigate={(href) => destinations.push(href)}> , @@ -283,7 +287,8 @@ describe("TechLog Studio dirty navigation", () => { const saved = await getSavedDocument(base); render( - gateway} navigate={(href) => destinations.push(href)}> + gateway} navigate={(href) => destinations.push(href)}> , diff --git a/tests/features/tech-log/studio-screens-smoke.test.tsx b/tests/features/tech-log/studio-screens-smoke.test.tsx index 6cace79..3e3a6b3 100644 --- a/tests/features/tech-log/studio-screens-smoke.test.tsx +++ b/tests/features/tech-log/studio-screens-smoke.test.tsx @@ -16,6 +16,7 @@ import { StudioDashboard } from "../../../src/features/tech-log/presentation/stu import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; afterEach(() => vi.restoreAllMocks()); @@ -30,6 +31,7 @@ function RouterStudioProvider({ const navigate = useNavigate(); return ( gateway} navigate={(href) => { void navigate(href); diff --git a/tests/features/tech-log/studio-shell-smoke.test.tsx b/tests/features/tech-log/studio-shell-smoke.test.tsx index 8af8833..87f8131 100644 --- a/tests/features/tech-log/studio-shell-smoke.test.tsx +++ b/tests/features/tech-log/studio-shell-smoke.test.tsx @@ -15,10 +15,11 @@ import { createExternalAuthSessionAdapter } from "../../../src/adapters/auth/ext import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; import { SessionProvider } from "../../../src/presentation/providers/session-provider.tsx"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; afterEach(() => vi.restoreAllMocks()); -function renderStudio( +async function renderStudio( initialEntry: string, createStudioGateway: () => StudioGateway, ) { @@ -62,12 +63,16 @@ function renderStudio( }, }); const view = render( + renderWithQueryProviders( , - ); + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + await screen.findByRole("main"); return { ...view, router }; } @@ -77,7 +82,7 @@ describe("TechLog Studio shell", () => { const gateway = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input.createStudioGateway(); const createGateway = vi.fn(() => gateway); - const { container, router } = renderStudio("/studio", createGateway); + const { container, router } = await renderStudio("/studio", createGateway); expect(await screen.findByRole("heading", { level: 1, name: "작업 흐름" })).toBeVisible(); expect(createGateway).toHaveBeenCalledTimes(1); @@ -90,6 +95,7 @@ describe("TechLog Studio shell", () => { ["작업본", "/studio/documents"], ["게시 기록", "/studio/publications"], ["새 문서", "/studio/documents/new"], + ["주제·프로젝트", "/studio/taxonomy"], ["공개 사이트 보기", "/"], ]); expect(screen.getByRole("link", { name: "공개 사이트 보기" })).toHaveAttribute("href", "/"); @@ -117,7 +123,7 @@ describe("TechLog Studio shell", () => { .mockReturnValueOnce(first) .mockReturnValueOnce(second); - renderStudio("/studio", createGateway); + await renderStudio("/studio", createGateway); await waitFor(() => expect(firstSignal).toBeDefined()); await user.click(screen.getByRole("button", { name: "Studio 메뉴 열기" })); expect(screen.getByRole("button", { name: "Studio 메뉴 닫기" })).toBeVisible(); @@ -131,12 +137,12 @@ describe("TechLog Studio shell", () => { expect(secondDashboard).toHaveBeenCalledTimes(1); }); - it("keeps unknown Studio routes inside the Studio shell without authentication UI", () => { + it("keeps unknown Studio routes inside the Studio shell without authentication UI", async () => { const createGateway = vi.fn( () => createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input.createStudioGateway(), ); - renderStudio("/studio/does-not-exist", createGateway); + await renderStudio("/studio/does-not-exist", createGateway); expect(screen.getByRole("banner")).toHaveClass("studio-header"); expect(screen.getByRole("heading", { level: 1, name: "Studio 화면을 찾을 수 없습니다" })).toBeVisible(); diff --git a/tests/features/tech-log/studio-validation-preview.test.tsx b/tests/features/tech-log/studio-validation-preview.test.tsx index 733b85e..d588d97 100644 --- a/tests/features/tech-log/studio-validation-preview.test.tsx +++ b/tests/features/tech-log/studio-validation-preview.test.tsx @@ -13,6 +13,7 @@ import { DocumentEditorScreen } from "../../../src/features/tech-log/presentatio import { PublicPreviewScreen } from "../../../src/features/tech-log/presentation/studio/components/public-preview-screen.tsx"; import { ValidationScreen } from "../../../src/features/tech-log/presentation/studio/components/validation-screen.tsx"; import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; class NoopIntersectionObserver implements IntersectionObserver { readonly root = null; @@ -44,7 +45,8 @@ function renderStudio( ) { return render( - gateway}>{child} + gateway}>{child} , ); } @@ -130,7 +132,8 @@ describe("TechLog Studio validation workflow", () => { view.rerender( - gateway}> + gateway}> , diff --git a/tests/features/tech-log/style-contract.test.ts b/tests/features/tech-log/style-contract.test.ts index e5588b6..962f27d 100644 --- a/tests/features/tech-log/style-contract.test.ts +++ b/tests/features/tech-log/style-contract.test.ts @@ -1,6 +1,6 @@ // @vitest-environment jsdom -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; @@ -14,6 +14,7 @@ import { FatalErrorState } from "../../../src/features/tech-log/presentation/pub import { PublicShell } from "../../../src/features/tech-log/presentation/public/public-shell.tsx"; import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; let styleElement: HTMLStyleElement; @@ -33,7 +34,7 @@ afterAll(() => { styleElement.remove(); }); -function renderPublicSurface(node: React.ReactNode) { +async function renderPublicSurface(node: React.ReactNode) { const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const shell = createElement(PublicShell, { children: node }); const router = createElement( @@ -41,19 +42,24 @@ function renderPublicSurface(node: React.ReactNode) { { initialEntries: ["/projects"] }, shell, ); - return render( - createElement(ApplicationProvider, { - application: createTestApplication({ - featureInputs: { "tech-log": techLog }, + const view = render( + renderWithQueryProviders( + createElement(ApplicationProvider, { + application: createTestApplication({ + featureInputs: { "tech-log": techLog }, + }), + children: router, }), - children: router, - }), + ), ); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 단언한다. + await screen.findByRole("main"); + return view; } describe("TechLog consumer-visible style contract", () => { - it("applies the source typography, palette, shell width, and header spacing", () => { - const view = renderPublicSurface( + it("applies the source typography, palette, shell width, and header spacing", async () => { + const view = await renderPublicSurface( createElement( "main", { id: "main-content", className: "shell" }, @@ -97,8 +103,8 @@ describe("TechLog consumer-visible style contract", () => { expect(getComputedStyle(wordmark).fontWeight).toBe("680"); }); - it("aligns code, table, and evidence widths with the article body", () => { - const view = renderPublicSurface( + it("aligns code, table, and evidence widths with the article body", async () => { + const view = await renderPublicSurface( createElement( "main", { id: "main-content", className: "shell" }, @@ -152,7 +158,7 @@ describe("TechLog consumer-visible style contract", () => { it("preserves keyboard focus and the real search-field focus style", async () => { const user = userEvent.setup(); - const view = renderPublicSurface( + const view = await renderPublicSurface( createElement( "main", { id: "main-content", className: "shell" }, @@ -181,8 +187,8 @@ describe("TechLog consumer-visible style contract", () => { ); }); - it("keeps every header and dialog control at the source minimum target size", () => { - const view = renderPublicSurface( + it("keeps every header and dialog control at the source minimum target size", async () => { + const view = await renderPublicSurface( createElement( "main", { id: "main-content", className: "shell" }, @@ -218,8 +224,8 @@ describe("TechLog consumer-visible style contract", () => { expect(getComputedStyle(dialogInner!).padding).toBe("27px 28px 22px"); }); - it("preserves footer spacing and fatal-action sizing", () => { - const view = renderPublicSurface( + it("preserves footer spacing and fatal-action sizing", async () => { + const view = await renderPublicSurface( createElement(FatalErrorState, { traceId: "PUBLIC-500", retryHref: "/", diff --git a/tests/features/tech-log/use-studio-asset-gateway.test.tsx b/tests/features/tech-log/use-studio-asset-gateway.test.tsx index b8a41f9..ace91d3 100644 --- a/tests/features/tech-log/use-studio-asset-gateway.test.tsx +++ b/tests/features/tech-log/use-studio-asset-gateway.test.tsx @@ -26,6 +26,8 @@ function contextValue( return { gateway: {} as never, assetGateway, + // 이 테스트는 Asset 접근자만 고정한다 — 관리 게이트웨이는 쓰이지 않으므로 자리만 채운다. + managementGateway: {} as never, resolvePublishedLabel: () => undefined, now: () => new Date("2026-08-14T01:00:00.000Z"), editor: null, diff --git a/tests/helpers/management-gateway-stub.ts b/tests/helpers/management-gateway-stub.ts new file mode 100644 index 0000000..7aa954a --- /dev/null +++ b/tests/helpers/management-gateway-stub.ts @@ -0,0 +1,28 @@ +import type { ManagementGateway } from "../../src/features/tech-log/application/ports/management-gateway.ts"; + +/** + * Studio 화면 테스트가 `StudioProvider` 를 렌더할 때 필요한 최소 관리 게이트웨이. + * + *

기본값은 "아무것도 없음" 이다. 이 하네스들은 문서·Asset 흐름을 검증하지 아니 주제·프로젝트 + * 관리를 검증하지 않으므로, 호출되면 빈 목록을 주고 쓰기는 거부한다 — 조용히 성공시키면 관리 + * 표면을 실수로 건드리는 테스트가 통과해 버린다. + */ +export function createManagementGatewayStub( + overrides: Partial = {}, +): ManagementGateway { + const unused = (name: string) => async (): Promise => { + throw new Error(`management gateway ${name} was not stubbed for this test`); + }; + return Object.freeze({ + listTopics: async () => [], + listProjects: async () => ({ items: [], page: {} }) as never, + createTopic: unused("createTopic"), + updateTopic: unused("updateTopic"), + deleteTopic: unused("deleteTopic"), + getProject: unused("getProject"), + createProject: unused("createProject"), + updateProject: unused("updateProject"), + deleteProject: unused("deleteProject"), + ...overrides, + }) as ManagementGateway; +} diff --git a/tests/unit/ci-workflow-generation.test.ts b/tests/unit/ci-workflow-generation.test.ts index 88e41c5..7e25b00 100644 --- a/tests/unit/ci-workflow-generation.test.ts +++ b/tests/unit/ci-workflow-generation.test.ts @@ -200,8 +200,8 @@ describe("CI gate contract", () => { // than taking either side's number. // Task 11 added TECH_LOG_STUDIO_ASSETS's manual accessibility evidence. // Alignment follow-up item 2 added the TechLog junit report. - expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(109); - expect(contract.artifacts).toHaveLength(130); + expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(110); + expect(contract.artifacts).toHaveLength(131); expect(contract.stages).toHaveLength(5); expect(contract.retention.classes).toHaveLength(5); expect(index.gates.get("FE-GATE-015")?.commandIds).toHaveLength(2); diff --git a/tests/unit/task3-selective-integration.test.ts b/tests/unit/task3-selective-integration.test.ts index fb11579..14c5543 100644 --- a/tests/unit/task3-selective-integration.test.ts +++ b/tests/unit/task3-selective-integration.test.ts @@ -192,7 +192,7 @@ describe("selective Task 3 contract closure", () => { expect(canonical.gates).toHaveLength(27); expect(canonical.commands).toHaveLength(85); expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(97); - expect(canonical.artifacts).toHaveLength(130); + expect(canonical.artifacts).toHaveLength(131); expect(canonical.stages).toHaveLength(5); expect(canonical.retention.classes).toHaveLength(5); diff --git a/tests/unit/tech-log-serving-contract.test.ts b/tests/unit/tech-log-serving-contract.test.ts index 70d7304..216de55 100644 --- a/tests/unit/tech-log-serving-contract.test.ts +++ b/tests/unit/tech-log-serving-contract.test.ts @@ -34,6 +34,7 @@ describe("TechLog production serving contract", () => { expect(contract.studioSpaPathPatterns).toEqual([ "^/studio$", "^/studio/assets$", + "^/studio/taxonomy$", "^/studio/documents$", "^/studio/documents/new$", "^/studio/documents/[^/]+/(edit|validation|preview|publish)$", diff --git a/vite.config.ts b/vite.config.ts index 9486e38..1142a83 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -31,6 +31,7 @@ const techLogRouteChunks = Object.freeze({ "/studio/pages/publications-page.tsx": "route-tech-log-studio-publications", "/studio/pages/publication-preview-page.tsx": "route-tech-log-studio-publication-preview", "/studio/pages/assets-page.tsx": "route-tech-log-studio-assets", + "/studio/pages/taxonomy-page.tsx": "route-tech-log-studio-taxonomy", "/studio/pages/studio-not-found-page.tsx": "route-tech-log-studio-not-found", "/public/pages/public-not-found-page.tsx": "route-not-found", } as const);