Add pages table + block registry scaffold (page builder step 2)
New `pages` table: slug/title/blocks(JSON-as-text)/status/protected, author FK, grouped SEO metadata + layout/nav settings columns (added up front per spec — cheap now, painful to retrofit), published_at mirroring posts. Block registry scaffold, server and client, defining the pattern without any block types yet (Wave 1 lands in step 3): - server/src/blocks: registry (register/get/list, reserved envelope keys, container metadata) + validateBlocks (authoritative save-time gate: envelope, registered-type, per-block schema, one-level nesting cap) + index entrypoint that will register Wave 1 defs. - client/src/blocks: mirror registry carrying renderer/editor/palette + makeBlockId, plus index entrypoint. Verified: schema applies idempotently against the dev DB (pages table + indexes present); validator exercised for empty/non-array/unknown-type/ bad-envelope/duplicate-id/nested-container cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -486,6 +486,43 @@ CREATE TABLE IF NOT EXISTS mod_notes (
|
||||
INDEX idx_mod_notes_user (discord_user_id, created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Generic CMS pages composed from a fixed palette of blocks (the page builder).
|
||||
-- `blocks` is a JSON array of block-envelope objects ({ id, type, version,
|
||||
-- visible, props }); it is stored as text and parsed/validated in app code
|
||||
-- against the block registry (server/src/blocks) on every save — the same
|
||||
-- pattern role_menus.mapping uses, since MariaDB's JSON type is just LONGTEXT and
|
||||
-- the driver hands it back as a string anyway. The seo_*/og_image/canonical_url/
|
||||
-- robots and layout/nav_* columns are metadata/settings surfaced grouped in the
|
||||
-- API response; several have no consumer yet but are cheap to add now and painful
|
||||
-- to retrofit once real pages exist. published_at mirrors posts: stamped the first
|
||||
-- time a page goes to 'published'.
|
||||
CREATE TABLE IF NOT EXISTS pages (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
slug VARCHAR(160) NOT NULL UNIQUE,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
blocks MEDIUMTEXT NOT NULL, -- JSON array of block objects
|
||||
status ENUM('draft','published') NOT NULL DEFAULT 'draft',
|
||||
protected TINYINT(1) NOT NULL DEFAULT 0,
|
||||
author_id INT NULL,
|
||||
-- SEO / social metadata (grouped under `metadata` in the API response).
|
||||
seo_title VARCHAR(200) NULL,
|
||||
meta_description VARCHAR(400) NULL,
|
||||
og_image VARCHAR(500) NULL,
|
||||
canonical_url VARCHAR(500) NULL,
|
||||
robots VARCHAR(100) NULL,
|
||||
-- Presentation / navigation (grouped under `settings` in the API response).
|
||||
layout ENUM('default','full_width','landing') NOT NULL DEFAULT 'default',
|
||||
show_in_nav TINYINT(1) NOT NULL DEFAULT 0,
|
||||
nav_group ENUM('main','footer','account','hidden') NULL,
|
||||
nav_order INT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
published_at DATETIME NULL,
|
||||
CONSTRAINT fk_pages_author FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_pages_status (status),
|
||||
INDEX idx_pages_nav (show_in_nav, nav_group, nav_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Migrations for databases created before the wiki upgrade. Each statement uses
|
||||
-- IF NOT EXISTS so re-running on every boot is a harmless no-op. New installs get
|
||||
-- these columns from the CREATE TABLE above; existing installs get them here.
|
||||
|
||||
Reference in New Issue
Block a user