FDN-01: repository & projektgerüst
Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/<ticket>-<slug>-Branches, Ticket-Prefix in Commit-Nachricht) etabliert.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
-- PROJ: external-share-links
|
||||
-- Doku-only (siehe README.md in diesem Verzeichnis) — die tatsächliche
|
||||
-- Ausführung passiert idempotent über internal/storage/shares.go
|
||||
-- initSharesSchema(), aufgerufen aus (*Store).New().
|
||||
--
|
||||
-- Externe Share-Links: tenant-scoped, ablaufender, optional passwortgeschützter
|
||||
-- öffentlicher Link auf genau EIN Dokument.
|
||||
-- * Token: 32 Byte crypto/rand, base64url; wird beim Erzeugen genau EINMAL
|
||||
-- zurückgegeben. Persistiert wird NUR der SHA-256-Hash (token_hash). Der
|
||||
-- öffentliche Abruf sucht immer über token_hash, nie über id.
|
||||
-- * expires_at ist Pflicht (kein unbegrenzter Share). password_hash optional
|
||||
-- (bcrypt, Cost 12).
|
||||
-- * Kein Hard-Delete: Revoke setzt nur revoked_at/revoked_by.
|
||||
-- * Prüfreihenfolge beim öffentlichen Abruf: revoked -> expired ->
|
||||
-- max_accesses erreicht -> Passwort -> ausliefern (access_count++ atomar
|
||||
-- mit WHERE-Guard gegen Race). Datei wird serverseitig aus dem WORM-Store
|
||||
-- gestreamt; storage_path/content_hash NIE im Response.
|
||||
-- * Jeder Zugriffsversuch (Erfolg/Fehlschlag) landet in
|
||||
-- document_share_accesses mit passendem result-Wert; die öffentlichen
|
||||
-- Endpunkte sind zusätzlich per-IP rate-limited (Token-Bucket in-memory).
|
||||
--
|
||||
-- Hinweis: tenant_id / created_by / revoked_by tragen bewusst KEINE FK auf
|
||||
-- tenants(id) bzw. users(id) — konsistent mit documents/taxonomy/permissions
|
||||
-- (plain BIGINT) und weil tenants/users von anderen Stores NACH storage.New()
|
||||
-- angelegt werden (Reihenfolge in cmd/archivdms/main.go). document_id behält
|
||||
-- seine FK, da documents die eigene Tabelle dieses Stores ist.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS document_shares (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
created_by BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
max_accesses INT,
|
||||
access_count INT NOT NULL DEFAULT 0,
|
||||
password_hash TEXT,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
revoked_by BIGINT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_document_shares_document ON document_shares(document_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_document_shares_tenant ON document_shares(tenant_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS document_share_accesses (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
share_id BIGINT NOT NULL REFERENCES document_shares(id) ON DELETE CASCADE,
|
||||
accessed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
result TEXT NOT NULL CHECK (result IN ('success','expired','revoked','max_reached','bad_password','rate_limited'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_share_accesses_share ON document_share_accesses(share_id, accessed_at DESC);
|
||||
Reference in New Issue
Block a user