feat: read the profile's topics from Studio, and add working-copy deletion
Two things an author could not control from Studio. The profile's "주요 관심 주제" was four strings in the JSX. Creating or removing a topic in Studio changed nothing, and correcting the list meant a rebuild and a redeploy. It now renders the published topic list. The old literal opened with "Backend Architecture", which no record in the catalogue actually carries — the profile was advertising a topic that did not exist, and nothing could have caught that while the list lived in the markup. The working-copy list gained a delete control. It routes by kind because the contract and the storage both do: Case and Reference share one table split by type, Question is its own. Decision has no delete — its lifecycle is accept, reject, supersede, which records what happened rather than erasing it — so the control does not appear for it. The list summary carries no version, so deletion reads the working copy first and uses the version it finds. A stale version from a list left open should fail as a conflict, not delete whatever is there now.
This commit is contained in:
@@ -29,8 +29,16 @@ function isAbortError(error: unknown): boolean {
|
||||
return error instanceof DOMException && error.name === "AbortError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decision 은 지울 수 없다. 계약에 삭제 operation 이 없고, 그건 누락이 아니라 판단이다 — 결정의
|
||||
* 수명주기는 수락·기각·대체이고 그 셋은 무슨 일이 있었는지 남기는 반면 삭제는 없앤다.
|
||||
*/
|
||||
const DELETABLE_KINDS = new Set(["CASE", "REFERENCE", "QUESTION"]);
|
||||
|
||||
export function DocumentList() {
|
||||
const { gateway } = useStudio();
|
||||
const { gateway, managementGateway, setRequestAnnouncement } = useStudio();
|
||||
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||
const [deleteError, setDeleteError] = useState("");
|
||||
const [searchDraft, setSearchDraft] = useState("");
|
||||
const [q, setQ] = useState("");
|
||||
const [kind, setKind] = useState<ListDocumentsQuery["kind"]>();
|
||||
@@ -42,6 +50,33 @@ export function DocumentList() {
|
||||
const [error, setError] = useState("");
|
||||
const [retryGeneration, setRetryGeneration] = useState(0);
|
||||
|
||||
/**
|
||||
* 목록 행에는 version 이 없다 (계약의 `DocumentSummary`). 삭제는 expectedVersion 을 요구하므로
|
||||
* 지우기 직전에 작업본을 한 번 읽어 그 시점의 version 을 쓴다 — 목록을 띄워 둔 채 다른 곳에서
|
||||
* 수정된 경우 여기서 409 로 걸리는 편이, 목록이 기억하던 낡은 version 으로 지우는 것보다 낫다.
|
||||
*/
|
||||
const removeDocument = async (item: DocumentPage["items"][number]) => {
|
||||
if (deletingId !== null) return;
|
||||
setDeletingId(item.id);
|
||||
setDeleteError("");
|
||||
try {
|
||||
const detail = await gateway.getDocument(item.id);
|
||||
await managementGateway.deleteDocument(
|
||||
item.kind as "CASE" | "REFERENCE" | "QUESTION",
|
||||
item.id,
|
||||
detail.document.version,
|
||||
);
|
||||
setRequestAnnouncement(`작업본 ${item.title || "제목 없음"} 을(를) 삭제했습니다.`);
|
||||
setRetryGeneration((value) => value + 1);
|
||||
} catch {
|
||||
setDeleteError(
|
||||
"삭제하지 못했습니다. 게시 중이거나, 이 기록을 참조하는 곳이 있거나, 다른 곳에서 먼저 수정되었을 수 있습니다.",
|
||||
);
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setLoading(true);
|
||||
@@ -169,6 +204,11 @@ export function DocumentList() {
|
||||
{page && !loading && !error ? (
|
||||
<>
|
||||
<p className="studio-result-count">{page.items.length}개의 작업본</p>
|
||||
{deleteError ? (
|
||||
<p className="studio-screen-error" role="alert">
|
||||
{deleteError}
|
||||
</p>
|
||||
) : null}
|
||||
{page.items.length ? (
|
||||
<div className="studio-document-list">
|
||||
{page.items.map((item) => (
|
||||
@@ -202,6 +242,16 @@ export function DocumentList() {
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{DELETABLE_KINDS.has(item.kind) ? (
|
||||
<button
|
||||
className="studio-secondary-button"
|
||||
type="button"
|
||||
disabled={deletingId !== null}
|
||||
onClick={() => void removeDocument(item)}
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user