Files
brain/db/migrations/008_connected_pages.sql
Andrew S Erwin 9db69efe4b
release gate / check (push) Failing after 7m7s
Add connected pages: hierarchy, backlinks, and live embeds
Implement Notion-like upgrade phase 3: owner-scoped nested pages with cycle rejection and child promotion on delete; Markdown-derived internal links and backlinks; live collection embeds resolved in the content-save transaction. Add migration 008, API/docs, integration coverage, and page/sidebar UI.
2026-10-19 22:37:21 -05:00

58 lines
2.6 KiB
SQL

-- 008_connected_pages.sql — Notion-like upgrades phase 3.
--
-- Nested pages, resolved internal page links (backlinks), and live collection
-- views embedded in a page. Links and embeds are derived from Markdown on
-- every content save: the page body stays the single source of truth.
--
-- Access rules:
-- * Pages remain owner-scoped. A link to a page the author does not own is
-- ignored, so backlinks can never reveal a title across accounts.
-- * An embed is stored only when the author owns the collection. The live
-- view still re-checks bookmark visibility through the collection item
-- endpoints (N-08); embedding never publishes anything.
--
-- Deleting:
-- * Deleting a parent page promotes its children (SET NULL), never deletes
-- them silently.
-- * Deleting a page cascades its outgoing/incoming links and embeds.
-- * Deleting a collection cascades its embeds; deleting a view clears the
-- embed's viewId so it falls back to the collection's default view.
ALTER TABLE "Page" ADD COLUMN IF NOT EXISTS "parentId" text;
-- The named constraint matches schema.sql so the fresh and upgraded paths
-- converge. The guard makes a re-run against an already-current schema a
-- no-op (without it, schema.sql's inline FK would collide).
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'Page_parentId_fkey' AND conrelid = '"Page"'::regclass
) THEN
ALTER TABLE "Page" ADD CONSTRAINT "Page_parentId_fkey"
FOREIGN KEY ("parentId") REFERENCES "Page"(id) ON DELETE SET NULL;
END IF;
END $$;
CREATE INDEX IF NOT EXISTS "Page_parentId_idx" ON "Page"("parentId");
CREATE TABLE IF NOT EXISTS "PageLink" (
"sourcePageId" text NOT NULL REFERENCES "Page"(id) ON DELETE CASCADE,
"targetPageId" text NOT NULL REFERENCES "Page"(id) ON DELETE CASCADE,
"createdAt" timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY ("sourcePageId", "targetPageId"),
CONSTRAINT "PageLink_no_self" CHECK ("sourcePageId" <> "targetPageId")
);
CREATE INDEX IF NOT EXISTS "PageLink_targetPageId_idx" ON "PageLink"("targetPageId");
CREATE TABLE IF NOT EXISTS "PageEmbed" (
"pageId" text NOT NULL REFERENCES "Page"(id) ON DELETE CASCADE,
"collectionId" text NOT NULL REFERENCES "Collection"(id) ON DELETE CASCADE,
"viewId" text REFERENCES "CollectionView"(id) ON DELETE SET NULL,
position integer NOT NULL DEFAULT 0,
"createdAt" timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY ("pageId", "collectionId")
);
CREATE INDEX IF NOT EXISTS "PageEmbed_collectionId_idx" ON "PageEmbed"("collectionId");
CREATE INDEX IF NOT EXISTS "PageEmbed_viewId_idx" ON "PageEmbed"("viewId");