All checks were successful
CD / Build and push images (push) Successful in 2m56s
CI / Lint, typecheck, test (push) Successful in 2m5s
CI / Auth e2e pack (push) Successful in 2m26s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 12s
Introduce pond-scoped hierarchical labels as the foundation for M4 organization and, later, M5 label-scoped permissions. - shared: `labels.ts` with the label schemas/views and the pure tree helpers (buildLabelTree, collectSubtreeIds, collectAncestorIds, labelDepth, subtreeHeight). These are the single hierarchy walk the label API and the future permission resolver both build on (permissions.md: a grant on a label applies to all its descendants). - prisma: `Label` (self-referential parent_id, unique per (pond, parent, name), cascade to subtree) and `PageLabel` assignment table; migration. - api: `LabelsService` + controller. Tree endpoint returns the hierarchy in one call; create/rename/recolor/move/delete and page assign/unassign. Validation: cycle prevention on move, depth limit 6, unique name per (pond, parent) — enforced under a per-pond advisory lock so root-label uniqueness holds despite Postgres treating NULL parents as distinct. Delete cascades the subtree and requires `?force=true` when pages are assigned. Assignment rejects labels from a different pond. Access gated through InterimAccessService on the owning pond. - i18n: label error codes and the colour validation message (de + en). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
42 lines
1.4 KiB
SQL
42 lines
1.4 KiB
SQL
-- CreateTable
|
|
CREATE TABLE "labels" (
|
|
"id" TEXT NOT NULL,
|
|
"pond_id" TEXT NOT NULL,
|
|
"parent_id" TEXT,
|
|
"name" TEXT NOT NULL,
|
|
"color" TEXT NOT NULL,
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
|
|
CONSTRAINT "labels_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "page_labels" (
|
|
"page_id" TEXT NOT NULL,
|
|
"label_id" TEXT NOT NULL,
|
|
|
|
CONSTRAINT "page_labels_pkey" PRIMARY KEY ("page_id","label_id")
|
|
);
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "labels_pond_id_idx" ON "labels"("pond_id");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "labels_pond_id_parent_id_name_key" ON "labels"("pond_id", "parent_id", "name");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "page_labels_label_id_idx" ON "page_labels"("label_id");
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "labels" ADD CONSTRAINT "labels_pond_id_fkey" FOREIGN KEY ("pond_id") REFERENCES "ponds"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "labels" ADD CONSTRAINT "labels_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "labels"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "page_labels" ADD CONSTRAINT "page_labels_page_id_fkey" FOREIGN KEY ("page_id") REFERENCES "pages"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "page_labels" ADD CONSTRAINT "page_labels_label_id_fkey" FOREIGN KEY ("label_id") REFERENCES "labels"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|