// ── Admin · Rust ────────────────────────────────────────────────────────── // // Mounted at `/api/v1/admin/rust`. The tier's gate is already applied: `admin` // sits behind `noindex, isLoggedIn, requireRole('admin','editor','moderator')`. // // **That gate is broader than these routes should be.** Editing a server row // means editing the credential that reaches a game host, which is an // administrator's job and not a moderator's — so the routes that write add // `requireRole('admin')` on top of the tier. A module adds per-route gates over // the tier gate and never re-implements it; this is what adding one looks like. // // ── The token is write-only ─────────────────────────────────────────────── // // `sidecarToken` is accepted and never returned. The list route reports // `hasToken` instead, because a blank field otherwise means both "unset" and // "set, and not being shown to you". An empty string on a save leaves the stored // value alone — an operator renaming a server must not have to re-paste a // credential, and a form that posts its own blank field would otherwise erase one // on every unrelated edit. const core = require('../../core') const express = core.express const admin = require('./rust.controller') const { requireRole, validate } = core.middleware const { body, param } = core.validator const adminRustRouter = express.Router() adminRustRouter.get( '/servers', // #swagger.tags = ['Admin · Rust'] // #swagger.summary = 'Every configured Rust server' // #swagger.description = 'The operator’s server rows with their sidecar URLs, whether a token is stored, and whether each sidecar was reachable on the last poll. The token itself is never returned.' /* #swagger.responses[200] = { description: 'The configured servers', content: { "application/json": { schema: { $ref: "#/components/schemas/RustAdminServerList" } } } } */ admin.listServers, ) adminRustRouter.put( '/servers/:id', // #swagger.tags = ['Admin · Rust'] // #swagger.summary = 'Create or update a Rust server' // #swagger.description = 'Writes one server row. `sidecarToken` is write-only — send it to set or rotate the credential, and omit it or send an empty string to leave the stored one untouched. The id is the slug every URL under the module carries.' /* #swagger.responses[204] = { description: 'Saved' } */ /* #swagger.responses[400] = { description: 'Invalid body' } */ requireRole('admin'), param('id') .matches(/^[a-z0-9][a-z0-9-]{0,63}$/) .withMessage('id must be lowercase letters, digits and hyphens'), body('name').isString().trim().isLength({ min: 1, max: 120 }), // A base URL is validated for SHAPE and not for reachability: an operator // configures a sidecar before installing it about half the time, and refusing // the row because nothing answers yet would make the obvious order of // operations impossible. body('sidecarBaseUrl').isURL({ require_tld: false, protocols: ['http', 'https'] }), body('sidecarToken').optional({ values: 'falsy' }).isString().isLength({ max: 512 }), body('protocol').optional().isInt({ min: 1, max: 1000 }).toInt(), body('enabled').optional().isBoolean().toBoolean(), body('sortOrder').optional().isInt({ min: -1000, max: 1000 }).toInt(), validate, admin.putServer, ) adminRustRouter.delete( '/servers/:id', // #swagger.tags = ['Admin · Rust'] // #swagger.summary = 'Remove a Rust server' // #swagger.description = 'Deletes the server row and the observed state that hangs off it. It does not touch the sidecar or the game host — those are removed with the installer.' /* #swagger.responses[204] = { description: 'Deleted' } */ requireRole('admin'), param('id').isString().isLength({ min: 1, max: 64 }), validate, admin.deleteServer, ) adminRustRouter.post( '/servers/:id/test', // #swagger.tags = ['Admin · Rust'] // #swagger.summary = 'Probe a server’s sidecar' // #swagger.description = 'Calls the sidecar’s health endpoint with the stored credential and reports what came back — whether it answered, whether the bridge plugin is connected to it, and which protocol version it speaks. This is the one route that tells a wrong URL from a wrong token from a mismatched version.' /* #swagger.responses[200] = { description: 'What the sidecar said', content: { "application/json": { schema: { $ref: "#/components/schemas/RustSidecarProbe" } } } } */ /* #swagger.responses[404] = { description: 'No such server' } */ requireRole('admin'), param('id').isString().isLength({ min: 1, max: 64 }), validate, admin.testServer, ) module.exports = adminRustRouter