Final phase of the wiki upgrade (see WIKI_UPGRADE.md). Schema (additive): wiki_revisions table (per-save content snapshots). The FULLTEXT index on wiki_pages(title, body) shipped in Phase 1. Search: - MATCH ... AGAINST natural-language search over title + body, ordered by relevance - public: GET /public/wiki?q= (published only); admin: GET /admin/wiki?q= (all statuses) - public wiki index gains a search box; admin list gains a search field Revision history: - every create/update snapshots the page into wiki_revisions - admin endpoints: list revisions, get one, and restore (restore overwrites the page, rebuilds links, and appends a new revision — history stays append-only); logged as wiki.revision.restore - editor gains a History modal: revision list + word-level diff (jsdiff) of a chosen revision against the current page, with one-click restore Verified end-to-end: search matches body and title; two edits produce three revisions; diff renders added/removed words; restore reverts and records a new revision. No console errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
131 lines
6.0 KiB
SQL
131 lines
6.0 KiB
SQL
-- UOMysticmoon database schema (MariaDB)
|
|
-- Run automatically by the MariaDB container (docker-entrypoint-initdb.d) on a
|
|
-- fresh volume, and idempotently by ensureSchema() on every server boot.
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
username VARCHAR(32) NOT NULL UNIQUE,
|
|
password_hash VARCHAR(72) NOT NULL,
|
|
role ENUM('admin','editor') NOT NULL DEFAULT 'admin',
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
last_login_at DATETIME NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
CREATE TABLE IF NOT EXISTS posts (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
category ENUM('news','five_on_friday','newsletter','screenshot') NOT NULL,
|
|
title VARCHAR(200) NOT NULL,
|
|
slug VARCHAR(220) NULL,
|
|
excerpt VARCHAR(400) NULL,
|
|
body MEDIUMTEXT NULL,
|
|
image_url VARCHAR(500) NULL,
|
|
published TINYINT(1) NOT NULL DEFAULT 0,
|
|
author_id 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_posts_author FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL,
|
|
INDEX idx_posts_feed (category, published, published_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
-- Wiki categories / sections. Defined before wiki_pages so the FK resolves on a
|
|
-- fresh install. Pages reference a category (nullable = "Uncategorized").
|
|
CREATE TABLE IF NOT EXISTS wiki_categories (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
slug VARCHAR(120) NOT NULL UNIQUE,
|
|
title VARCHAR(200) NOT NULL,
|
|
description VARCHAR(400) NULL,
|
|
sort_order INT NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
CREATE TABLE IF NOT EXISTS wiki_pages (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
slug VARCHAR(120) NOT NULL UNIQUE,
|
|
title VARCHAR(200) NOT NULL,
|
|
body MEDIUMTEXT NULL,
|
|
excerpt VARCHAR(400) NULL,
|
|
category_id INT NULL,
|
|
published TINYINT(1) NOT NULL DEFAULT 1,
|
|
sort_order INT NOT NULL DEFAULT 0,
|
|
updated_by 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_wiki_user FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_wiki_category FOREIGN KEY (category_id) REFERENCES wiki_categories(id) ON DELETE SET NULL,
|
|
FULLTEXT INDEX idx_wiki_search (title, body)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
-- Wiki tags (many-to-many with pages).
|
|
CREATE TABLE IF NOT EXISTS wiki_tags (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
slug VARCHAR(120) NOT NULL UNIQUE,
|
|
label VARCHAR(120) NOT NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
CREATE TABLE IF NOT EXISTS wiki_page_tags (
|
|
page_id INT NOT NULL,
|
|
tag_id INT NOT NULL,
|
|
PRIMARY KEY (page_id, tag_id),
|
|
CONSTRAINT fk_wpt_page FOREIGN KEY (page_id) REFERENCES wiki_pages(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_wpt_tag FOREIGN KEY (tag_id) REFERENCES wiki_tags(id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
-- Internal-link index, rebuilt on each save. target_slug may point at a page
|
|
-- that does not exist yet (a "red link").
|
|
CREATE TABLE IF NOT EXISTS wiki_links (
|
|
source_page_id INT NOT NULL,
|
|
target_slug VARCHAR(120) NOT NULL,
|
|
CONSTRAINT fk_wiki_links_src FOREIGN KEY (source_page_id) REFERENCES wiki_pages(id) ON DELETE CASCADE,
|
|
INDEX idx_wiki_links_target (target_slug)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
-- Per-save content snapshots for history / diff / restore.
|
|
CREATE TABLE IF NOT EXISTS wiki_revisions (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
page_id INT NOT NULL,
|
|
title VARCHAR(200) NOT NULL,
|
|
body MEDIUMTEXT NULL,
|
|
excerpt VARCHAR(400) NULL,
|
|
category_id INT NULL,
|
|
editor_id INT NULL,
|
|
change_note VARCHAR(280) NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_wiki_rev_page FOREIGN KEY (page_id) REFERENCES wiki_pages(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_wiki_rev_editor FOREIGN KEY (editor_id) REFERENCES users(id) ON DELETE SET NULL,
|
|
INDEX idx_wiki_rev_page (page_id, id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
`key` VARCHAR(64) PRIMARY KEY,
|
|
value TEXT NULL,
|
|
updated_by INT NULL,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_settings_user FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
CREATE TABLE IF NOT EXISTS activity_log (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
user_id INT NULL,
|
|
action VARCHAR(64) NOT NULL,
|
|
detail TEXT NULL,
|
|
ip VARCHAR(45) NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_activity_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
|
|
INDEX idx_activity_created (created_at)
|
|
) 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.
|
|
-- (The category foreign key is only added on fresh installs; on upgraded databases
|
|
-- referential integrity for category_id is enforced in application code.)
|
|
ALTER TABLE wiki_pages ADD COLUMN IF NOT EXISTS excerpt VARCHAR(400) NULL;
|
|
ALTER TABLE wiki_pages ADD COLUMN IF NOT EXISTS category_id INT NULL;
|
|
ALTER TABLE wiki_pages ADD COLUMN IF NOT EXISTS published TINYINT(1) NOT NULL DEFAULT 1;
|
|
ALTER TABLE wiki_pages ADD COLUMN IF NOT EXISTS sort_order INT NOT NULL DEFAULT 0;
|
|
ALTER TABLE wiki_pages ADD COLUMN IF NOT EXISTS published_at DATETIME NULL;
|
|
ALTER TABLE wiki_pages ADD FULLTEXT INDEX IF NOT EXISTS idx_wiki_search (title, body);
|