refactor(modules)!: move the UO server half out to module-uo

40 files, ~9,674 lines, 27 of 68 tables. Core no longer contains anything that
knows what a shard is.

BREAKING for a deployment only in the sense that the module must be installed
for these URLs to answer -- no URL moved. routes.manifest.json goes 228 -> 158
public routes here, and the 70 that left reappear byte-identical when the module
is loaded: verified by generating the manifest against core+module and diffing
it against the pre-extraction file. Zero missing, zero added, and routes.guards
identical across all 228, so no auth gate moved either.

The five tier mounts are gone from public/admin/player index.js and are still
served: the loader mounts them onto the same routers after every core mount.
That ordering is also what keeps the prefixes unclaimable -- the collision check
asks the live router what core owns, so a second module claiming /shard is
rejected against the mounts actually present rather than against a list.

server.js loses its eight UO call sites to the module's onBoot/onShutdown.
schema.sql loses its 27 shard_*/uo_link_* statements; the two that FK into users
are why the fragment replays AFTER core's schema, and no core table ever
referenced a module table, which is what makes core still able to boot alone.

Verified against a running server with the module installed: it loads, mounts
five prefixes, replays 35 statements, warms up and reaches `started`; public
shard and atlas routes answer 200 with real data (800 creatures, 6,455
spawners), admin and player answer 401 from core's tier gates, and the extension
slot answers at /admin/users/:id/shard/*. Core's own SPA renders the shard and
atlas pages unchanged against the module-served API, with no console errors and
no CSP reports.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-11 12:08:03 -05:00
committed by Claude
parent f50541f374
commit 39d731d87a
9 changed files with 27 additions and 5223 deletions

View File

@@ -27,8 +27,6 @@ const postsRouter = require('./posts.router')
const uploadsRouter = require('./uploads.router')
const wikiRouter = require('./wiki.router')
const pagesRouter = require('./pages.router')
const shardRouter = require('./shard.router')
const uoLinkRouter = require('./uoLink.router')
const emailRouter = require('./email.router')
const discordBotRouter = require('./discordBot.router')
const settingsRouter = require('./settings.router')
@@ -64,12 +62,14 @@ adminRouter.use('/posts', postsRouter)
adminRouter.use('/uploads', uploadsRouter)
adminRouter.use('/wiki', wikiRouter)
adminRouter.use('/pages', pagesRouter)
// Ops and configuration. /shard mixes tiers on one prefix — self-service game
// account linking (no extra gate) alongside modAccess in-game staff ops — so
// one router owns the prefix and gates per route. The rest are admin-only.
// /admin/shard/pages is the in-game help-page queue, unrelated to /admin/pages.
adminRouter.use('/shard', shardRouter)
adminRouter.use('/uo-link', uoLinkRouter)
// Ops and configuration.
//
// `/shard` and `/uo-link` are absent here and are still served: they are
// module-uo's, mounted onto this same router by the loader after every core
// mount above (MODULE_API.md §2.4). The URLs did not move — the code did. That
// ordering is also what makes the prefixes unclaimable by anyone else: the
// loader asks this live router what core owns, so a second module claiming
// `/shard` is rejected against the mounts actually present, not against a list.
adminRouter.use('/email', emailRouter)
adminRouter.use('/discord-bot', discordBotRouter)
adminRouter.use('/settings', settingsRouter)

View File

@@ -13,7 +13,9 @@
// Adding a requireRole('player') here would 403 an admin off their own characters
// (it happened once — see docs/website/BACKEND_DESIGN.md). Staff also reach the
// identical self-scoped handlers under /admin/shard and /auth/me/account; those
// are alternative URLs onto the same controllers, not duplicated logic.
// are alternative URLs onto the same controllers, not duplicated logic — and
// both of those live in module-uo now, which changes where they are defined and
// nothing about which URLs answer.
//
// See docs/website/API_V2_PLAN.md § Phase 2 for the split.
@@ -23,7 +25,6 @@ const { requireAuth } = require('../../../auth/session.middleware')
const noindex = require('../../../middleware/noindex')
const accountRouter = require('./account.router')
const shardRouter = require('./shard.router')
const appealsRouter = require('./appeals.router')
const playerRouter = express.Router()
@@ -37,7 +38,6 @@ const playerRouter = express.Router()
playerRouter.use(noindex, requireAuth)
playerRouter.use('/account', accountRouter)
playerRouter.use('/shard', shardRouter)
playerRouter.use('/appeals', appealsRouter)
module.exports = playerRouter

View File

@@ -20,8 +20,6 @@ const express = require('express')
const postsRouter = require('./posts.router')
const wikiRouter = require('./wiki.router')
const pagesRouter = require('./pages.router')
const shardRouter = require('./shard.router')
const atlasRouter = require('./atlas.router')
const modulesRouter = require('./modules.router')
const siteRouter = require('./site.router')
@@ -32,13 +30,6 @@ const publicRouter = express.Router()
publicRouter.use('/posts', postsRouter)
publicRouter.use('/wiki', wikiRouter)
publicRouter.use('/pages', pagesRouter)
// Live shard data, never site-mode gated.
publicRouter.use('/shard', shardRouter)
// The spawn atlas: static shard CONTENT, parsed from the shard's ServUO tree
// rather than fetched from the sidecar. Deliberately not under /shard — nothing
// here depends on the bridge — and site-mode gated per route like the content
// routers above, which is the other half of that distinction.
publicRouter.use('/atlas', atlasRouter)
// What this backend serves beyond core. A real prefix layer rather than a fifth
// singleton in site.router.js, because the loader's prefix-collision probe reads
// the live tier stack and skips root-mounted layers — this mount is what makes