Kern-Entitaeten (Dokument, Datei-Revision, Ordner, Tag, Metadatenfeld) als tenant-scoped SQL-Migration (Modell C, keine tenant_id-Spalte), FK auf users(id) aus Core IAM-01 (Auth bleibt vollstaendig in Core). Eigener, minimaler Migrations-Runner (internal/migrate, kein ORM) mit schema_migrations-Tracking fuer Idempotenz. Seed-Skript fuer Entwicklung. Auf 192.168.1.131 verifiziert: Migration auf leerer+bestehender DB, Rollback stellt Vorzustand wieder her, 4 FK-Negativtests, Seed-Skript end-to-end gegen frische DB. go.sum committet (Lock-Datei-Lehre aus dem Ticket). build/vet/lint/test clean. Siehe dms/docs/FDN-02-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
89 lines
4.1 KiB
SQL
89 lines
4.1 KiB
SQL
-- Kern-Entitaeten des DMS (FDN-02): Dokument, Datei-Revision, Ordner, Tag,
|
|
-- Metadatenfeld. Laeuft in der DB EINES Mandanten (Modell C, siehe Core
|
|
-- TEN-01) — keine tenant_id-Spalte, die Tenant-Zugehoerigkeit ist implizit
|
|
-- durch die Datenbankverbindung gegeben. FK auf users(id) spiegelt das
|
|
-- Benutzer-Datenmodell aus Core IAM-01 (migrations/tenant/0001_users.up.sql
|
|
-- im NEXARCH-Core-Modul) — Auth/Benutzerverwaltung liegt vollstaendig in
|
|
-- Core (siehe "Nicht Bestandteil" in FDN-02), diese Migration dupliziert sie
|
|
-- NICHT, sondern setzt sie als bereits vorhanden voraus (users-Tabelle wird
|
|
-- durch Cores eigene Migration in derselben physischen Tenant-Datenbank
|
|
-- angelegt, bevor DMS-Migrationen laufen).
|
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
CREATE TABLE folders (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
parent_folder_id UUID REFERENCES folders(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX idx_folders_parent_folder_id ON folders(parent_folder_id);
|
|
|
|
CREATE TABLE documents (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,
|
|
title TEXT NOT NULL,
|
|
-- current_revision_id verweist erst NACH der Anlage von file_revisions
|
|
-- auf eine Zeile (siehe ALTER TABLE unten) — beim INSERT eines Dokuments
|
|
-- existiert noch keine Revision, daher NULLable und zirkulaer per
|
|
-- nachtraeglichem FOREIGN KEY statt Inline-Referenz geloest.
|
|
current_revision_id UUID,
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
deleted_at TIMESTAMPTZ
|
|
);
|
|
CREATE INDEX idx_documents_folder_id ON documents(folder_id);
|
|
CREATE INDEX idx_documents_created_by ON documents(created_by);
|
|
|
|
CREATE TABLE file_revisions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
revision_number INT NOT NULL,
|
|
-- storage_key ist ein Platzhalter fuer die Objekt-Storage-Abstraktion
|
|
-- (FDN-03, "Nicht Bestandteil" dieser Kachel) — hier nur die Spalte, die
|
|
-- spaetere Kachel legt fest, was tatsaechlich dahinter liegt.
|
|
storage_key TEXT NOT NULL,
|
|
checksum_sha256 TEXT NOT NULL,
|
|
size_bytes BIGINT NOT NULL,
|
|
mime_type TEXT NOT NULL,
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (document_id, revision_number)
|
|
);
|
|
CREATE INDEX idx_file_revisions_document_id ON file_revisions(document_id);
|
|
|
|
ALTER TABLE documents
|
|
ADD CONSTRAINT fk_documents_current_revision
|
|
FOREIGN KEY (current_revision_id) REFERENCES file_revisions(id) ON DELETE SET NULL;
|
|
|
|
CREATE TABLE tags (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL UNIQUE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE document_tags (
|
|
document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (document_id, tag_id)
|
|
);
|
|
CREATE INDEX idx_document_tags_tag_id ON document_tags(tag_id);
|
|
|
|
CREATE TABLE metadata_fields (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
field_key TEXT NOT NULL UNIQUE,
|
|
label TEXT NOT NULL,
|
|
field_type TEXT NOT NULL CHECK (field_type IN ('text', 'number', 'date', 'bool', 'select')),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE document_metadata_values (
|
|
document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
field_id UUID NOT NULL REFERENCES metadata_fields(id) ON DELETE CASCADE,
|
|
value TEXT NOT NULL,
|
|
PRIMARY KEY (document_id, field_id)
|
|
);
|
|
CREATE INDEX idx_document_metadata_values_field_id ON document_metadata_values(field_id);
|