Add trust features: history, trash, export, and collection sharing
release gate / check (push) Failing after 6m36s
release gate / check (push) Failing after 6m36s
Implement Notion-like upgrade phase 4: append-only page revisions with restore-as-a-new-version; recoverable bookmark/page/collection deletions whose restores never resurrect public flags or shares; owner-scoped JSON export; and explicit, revocable read-only collection sharing with owner-only mutations and no transitive third-party bookmark leaks. Add migration 009, API/docs, integration coverage, and page/collections/trash UI.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
-- 009_trust.sql — Notion-like upgrades phase 4 (trust).
|
||||
--
|
||||
-- Server-side page revision history, recoverable deletions, and explicit
|
||||
-- collection sharing.
|
||||
--
|
||||
-- Trust rules:
|
||||
-- * Every committed page content version is snapshotted. Restoring a
|
||||
-- revision writes a NEW version (history is append-only) and the restore
|
||||
-- itself is recorded.
|
||||
-- * Trash is a per-owner snapshot taken in the same transaction as the
|
||||
-- delete. It restores intrinsic data and owner-scoped relationships, but
|
||||
-- never access grants (bookmark public flags, bookmark/tag shares,
|
||||
-- collection shares). A granted access revoked while an item was in the
|
||||
-- trash cannot come back with the item.
|
||||
-- * Collection sharing is read-only and revocable. Recipients see member
|
||||
-- bookmarks through the narrowed shared projection; mutations stay
|
||||
-- owner-only. BookmarkVisible gains the explicit collection-share edge.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "PageRevision" (
|
||||
id text PRIMARY KEY,
|
||||
"pageId" text NOT NULL REFERENCES "Page"(id) ON DELETE CASCADE,
|
||||
version integer NOT NULL,
|
||||
title text NOT NULL,
|
||||
content text NOT NULL,
|
||||
icon text,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE ("pageId", version)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "PageRevision_pageId_version_idx" ON "PageRevision"("pageId", version DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "Trash" (
|
||||
id text PRIMARY KEY,
|
||||
"ownerId" text NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
|
||||
type text NOT NULL CHECK (type IN ('bookmark','page','collection')),
|
||||
"originalId" text NOT NULL,
|
||||
data jsonb NOT NULL,
|
||||
"deletedAt" timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (type, "originalId")
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "Trash_ownerId_deletedAt_idx" ON "Trash"("ownerId", "deletedAt" DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "CollectionShare" (
|
||||
"collectionId" text NOT NULL REFERENCES "Collection"(id) ON DELETE CASCADE,
|
||||
"userId" text NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
|
||||
"createdAt" timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY ("collectionId", "userId")
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "CollectionShare_userId_idx" ON "CollectionShare"("userId");
|
||||
Reference in New Issue
Block a user