// /api/v1/public — the anonymous public surface, assembled from per-capability // routers. // // This file owns the mount table and nothing else; no route is declared here. // Each capability router mounts at the prefix it already owned inside the old // monolithic public.routes.js, so the emitted URL set is byte-identical — proved // by a zero-line diff in server/routes.manifest.json (`npm run routes:manifest`). // // **There is deliberately no group gate.** Unlike /admin (staffOnly) and /player // (requireAuth), this group is unauthenticated by design and must stay that way: // the SPA renders logged-out, the Discord bot reads it anonymously, and the // Android app's ShardStreamClient consumes /public/shard/stream with no // Authorization header. Content visibility during maintenance is handled by the // per-route `siteMode` middleware, not by an auth gate. // // See docs/website/API_V2_PLAN.md § Phase 2 for the split. const express = require('express') const postsRouter = require('./posts.router') const wikiRouter = require('./wiki.router') const pagesRouter = require('./pages.router') const modulesRouter = require('./modules.router') const teamsRouter = require('./teams.router') const eventsRouter = require('./events.router') const engagementRouter = require('./engagement.router') const siteRouter = require('./site.router') const publicRouter = express.Router() // Content. All three are site-mode gated per route (the /pages draft-preview // route is the one deliberate exception — see pages.router.js). publicRouter.use('/posts', postsRouter) publicRouter.use('/wiki', wikiRouter) publicRouter.use('/pages', pagesRouter) // 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 // /modules unclaimable by a module. Never site-mode gated: a client must be able // to feature-detect while the site is in maintenance. publicRouter.use('/modules', modulesRouter) // Teams. A core prefix, not a module's: the entity is core's even though a module // is what populates it (TEAMS.md §10.3). Site-mode gated per route, like the // content above it. publicRouter.use('/teams', teamsRouter) // Events. A core prefix like /teams: the calendar, the event page and the arc // are core's surface even when every step an event dispatches belongs to a // module. Site-mode gated per route, like the content above it. publicRouter.use('/events', eventsRouter) // The unauthenticated half of the engagement system: today exactly the // unsubscribe pair. Its own prefix rather than a Teams sub-path, because what a // token names is a channel and a scope and a scope is not always a Team // (ENGAGEMENT.md Phase 6). Never site-mode gated — an unsubscribe has to work // while the site is in maintenance. publicRouter.use('/engagement', engagementRouter) // The four singletons that own no path segment of their own: /settings, /status, // /version and /contact. Mounted at the group root, last — safe only because // site.router.js declares no router-level middleware (a bare `use(gate)` in a // root-mounted router runs for every request passing through toward another // mount). Same arrangement as admin/dashboard.router.js. publicRouter.use('/', siteRouter) module.exports = publicRouter