Add connected pages: hierarchy, backlinks, and live embeds
release gate / check (push) Failing after 7m7s
release gate / check (push) Failing after 7m7s
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.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
-- 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");
|
||||
Reference in New Issue
Block a user