dorfteich/apps/api/prisma/migrations/20260709082418_labels/migration.sql
Claude Opus 4.8 a3a012c41d
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
Add hierarchical labels: model, CRUD API, and validation (#43)
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
2026-07-09 10:30:17 +02:00

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;