feat(share-links): add frozen document-membership join (migration 0229)

P3-1-T1. Introduce ClinicianShareLinkDocument, the write-once join binding a
stored document to a clinician share link. The document set a share exposes is
a relation, not a mutable column, so it inherits the link's frozen-scope
discipline. Both foreign keys cascade so a deleted link or document takes its
membership with it.
This commit is contained in:
Marc-André Bombeck committed 2026-07-07 18:34:58 +02:00
1 parent 1474c87011
commit 32f51b4299
2 files changed
+77 -1

No files matched your search

@@ -0,0 +1,45 @@
-- Document vault, Phase 3: attach hand-picked documents to a clinician share
-- link.
--
-- `clinician_share_link_documents` — frozen, write-once membership rows
-- binding a stored document to a share link. The document set a share
-- exposes is modelled as this join, never a mutable column on the link, so
-- it inherits the link's frozen-scope discipline: rows are created in the
-- same transaction as the link, each document is owner-checked at create,
-- and the set is never widened afterwards. The public share serve route
-- serves ONLY a document id present in this set for the resolved token.
--
-- Both foreign keys cascade: deleting the link or the underlying document
-- removes the membership cleanly (a shared document the owner later deletes
-- simply disappears from the link).
--
-- Additive; applies clean on a 0228 database and on a fresh database as part
-- of the ordered migration chain.
CREATE TABLE "clinician_share_link_documents" (
"id" TEXT NOT NULL,
"share_link_id" TEXT NOT NULL,
"document_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "clinician_share_link_documents_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "clinician_share_link_documents_share_link_id_document_id_key"
ON "clinician_share_link_documents" ("share_link_id", "document_id");
CREATE INDEX "clinician_share_link_documents_share_link_id_idx"
ON "clinician_share_link_documents" ("share_link_id");
CREATE INDEX "clinician_share_link_documents_document_id_idx"
ON "clinician_share_link_documents" ("document_id");
ALTER TABLE "clinician_share_link_documents"
ADD CONSTRAINT "clinician_share_link_documents_share_link_id_fkey"
FOREIGN KEY ("share_link_id") REFERENCES "clinician_share_links" ("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "clinician_share_link_documents"
ADD CONSTRAINT "clinician_share_link_documents_document_id_fkey"
FOREIGN KEY ("document_id") REFERENCES "inbound_documents" ("id")
ON DELETE CASCADE ON UPDATE CASCADE;
+32 -1
View File
@@ -4852,13 +4852,41 @@ model ClinicianShareLink {
lastAccessAt DateTime? @map("last_access_at")
accessCount Int @default(0) @map("access_count")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
/// v1.28 — the hand-picked document set this link exposes (0..N). A relation,
/// not a column: frozen write-once at create, owner-checked, never widened.
documents ClinicianShareLinkDocument[]
@@index([userId, createdAt(sort: Desc)])
@@index([expiresAt])
@@map("clinician_share_links")
}
/// v1.28 (document vault, Phase 3) — frozen, write-once membership binding a
/// stored document to a clinician share link. The document set a share exposes
/// is modelled as this relation, never a mutable column: the rows are created
/// in the same transaction as the link, each `documentId` is validated as the
/// caller's own live document at create time, and the set is never widened
/// afterwards. The public share serve route serves ONLY a document id present
/// in this set for the resolved token — a guessed / foreign id is a flat 404.
/// Both foreign keys cascade: deleting the link or the underlying document
/// removes the membership cleanly (a shared document the owner later deletes
/// simply disappears from the link).
model ClinicianShareLinkDocument {
id String @id @default(cuid())
shareLinkId String @map("share_link_id")
documentId String @map("document_id")
createdAt DateTime @default(now()) @map("created_at")
shareLink ClinicianShareLink @relation(fields: [shareLinkId], references: [id], onDelete: Cascade)
document InboundDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
@@unique([shareLinkId, documentId])
@@index([shareLinkId])
@@index([documentId])
@@map("clinician_share_link_documents")
}
// v1.11.0 W3 — period-narrative cache (Pillar P1).
//
// One durable, typed row per (user, period, locale) carrying the latest
@@ -5815,6 +5843,9 @@ model InboundDocument {
facts ExtractedFact[]
conditionLinks DocumentConditionLink[]
/// v1.28 — share-link memberships (m:n via `ClinicianShareLinkDocument`).
/// Cascades: deleting this document removes it from every share it was on.
shareLinks ClinicianShareLinkDocument[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")