Wiki Phase 1: categories, drafts/publish, HTML sanitization

Foundation & safety phase of the wiki upgrade (see WIKI_UPGRADE.md).

Schema (additive, idempotent via ensureSchema):
- new wiki_categories table; wiki_pages gains category_id, excerpt,
  published, published_at, sort_order, and a FULLTEXT index
- migration ALTERs guarded with IF NOT EXISTS for existing databases
- seed reworked into 4 sections with the 8 starter pages assigned

Security:
- new utils/sanitizeHtml.js (sanitize-html allowlist); wiki bodies are
  sanitized on every save, and the article renders through DOMPurify
- strips <script>, event handlers (onerror), and javascript: URLs

Backend:
- public: published-only list with ?category filter + /wiki/categories
- admin: extended page CRUD, PATCH publish toggle, category CRUD;
  drafts visible to admin, hidden from public
- all writes logged to activity_log

Frontend:
- data-driven public wiki index (sections + real descriptions; removed
  hardcoded blurbs/Roman numerals) with ?category filtering
- article: category breadcrumb + sanitized render
- admin: Section/Status columns, draft/publish + section + excerpt in the
  editor, and a Manage sections modal

Verified end-to-end against MariaDB 11: migration clean, XSS neutralized,
drafts hidden, client builds, server boots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 10:45:21 -05:00
parent dd1f61222d
commit b925114923
20 changed files with 1237 additions and 100 deletions

View File

@@ -22,24 +22,39 @@ const DEFAULT_SETTINGS = {
site_title: 'UOMysticmoon',
}
// The 8 starter wiki categories (editable later via the admin panel).
// Starter wiki sections (editable later via the admin panel).
// [slug, title, description, sort_order]
const WIKI_CATEGORIES = [
['guides', 'Guides', 'Getting started and how-to guides.', 10],
['world', 'World & Lore', 'Regions, maps, and the story of Mysticmoon.', 20],
['gameplay', 'Systems & Gameplay', 'Mechanics, items, monsters, and crafting.', 30],
['community', 'Community & Rules', 'Player conduct and shard policies.', 40],
]
// The 8 starter pages, each mapped to a section. [slug, title, body, categorySlug]
const WIKI_PAGES = [
['new-player-guide', 'New Player Guide', 'First steps, basic survival, and early goals.'],
['maps-atlas', 'Maps & Atlas', 'Regions, towns, routes, and travel notes.'],
['systems', 'Server Systems', 'Shard mechanics and custom features.'],
['items', 'Items & Rewards', 'Equipment, treasures, rewards, and curiosities.'],
['monsters', 'Monsters & Encounters', 'Creatures, bosses, spawns, and dangers.'],
['crafting', 'Crafting', 'Professions, materials, recipes, and tools.'],
['lore', 'Lore', 'Stories, places, factions, and mysteries.'],
['rules', 'Rules', 'Player conduct, shard expectations, and policies.'],
['new-player-guide', 'New Player Guide', 'First steps, basic survival, and early goals.', 'guides'],
['maps-atlas', 'Maps & Atlas', 'Regions, towns, routes, and travel notes.', 'world'],
['lore', 'Lore', 'Stories, places, factions, and mysteries.', 'world'],
['systems', 'Server Systems', 'Shard mechanics and custom features.', 'gameplay'],
['items', 'Items & Rewards', 'Equipment, treasures, rewards, and curiosities.', 'gameplay'],
['monsters', 'Monsters & Encounters', 'Creatures, bosses, spawns, and dangers.', 'gameplay'],
['crafting', 'Crafting', 'Professions, materials, recipes, and tools.', 'gameplay'],
['rules', 'Rules', 'Player conduct, shard expectations, and policies.', 'community'],
]
async function seedDefaults() {
for (const [key, value] of Object.entries(DEFAULT_SETTINGS)) {
await settingsDb.seedDefault(key, value)
}
for (const [slug, title, body] of WIKI_PAGES) {
for (const [slug, title, description, sortOrder] of WIKI_CATEGORIES) {
await wikiDb.seedDefaultCategory(slug, title, description, sortOrder)
}
for (const [slug, title, body, categorySlug] of WIKI_PAGES) {
await wikiDb.seedDefault(slug, title, body)
// Attach to its section (only if not already categorized — safe re-run /
// migration of pages seeded before the wiki upgrade).
await wikiDb.assignCategoryBySlug(slug, categorySlug)
}
log.info('settings and wiki defaults ensured')
}