// ── The `admin.users.detail` extension slot ─────────────────────────────── // // R13's first slot, and the phase criterion in one file: *an operator sees the // Steam id inside core's own user page*. // // MODULE_API.md §2.4's fourth mount shape — module routes hanging off a CORE // resource. `/admin/users/:id` is a URL core owns and this module has something // to say about it, so the routes cannot move behind a `/rust` prefix and cannot // be registered anywhere else either. Core declares the slot; a module fills it, // and only one module may. // // Three things about this router that are not true of the other three: // // • **`mergeParams: true`**, because the user id belongs to the parent. Without // it `req.params.id` is undefined and every statement here silently scopes to // nothing. // • **The paths keep the module's own segment** (`/rust/links`, not `/links`). // Core owns the resource and other modules may fill their own slots on other // resources; a bare `/links` would be this module claiming a word on a URL it // does not own. // • **The gate is stricter than the admin tier's.** Core's users router is // `requireRole('admin')` and the slot is mounted inside it, so editors and // moderators never reach here — which is right for a surface that can sever // what phases 7 and 13 grant against. // // The client half is registered under the SAME name (`registry.registerExtension` // in `entry.jsx`) and builds its own client for these two routes; a slot passes a // component `userId` and nothing else. const core = require('../../core') const express = core.express const { param } = core.validator const usersRust = require('./usersRust.controller') const { validate } = core.middleware // Same bound the player tier states, for the same reason: nothing but digits // reaches a `WHERE steam_id = ?`. const STEAM_ID_RE = /^[0-9]{5,32}$/ const usersRustRouter = express.Router({ mergeParams: true }) usersRustRouter.get( '/rust/links', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'A user’s linked Steam accounts and their Rust record (admin only)' // #swagger.description = 'Every Steam account linked to this website user, with the display name the game last saw and, per server, all-time kills / deaths / playtime across every wipe. Fills the admin.users.detail extension slot.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'Linked accounts', content: { "application/json": { schema: { $ref: "#/components/schemas/RustAdminLinkList" } } } } */ param('id').isInt(), validate, usersRust.listLinks, ) usersRustRouter.delete( '/rust/links/:steamId', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Sever a user’s Steam link (admin only)' // #swagger.description = 'Staff release a link on this user’s behalf. It is the counterweight to the site refusing to move a Steam id another account holds: a player who cannot reach that Steam account in game has no other way back. Recorded in the activity log.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } // #swagger.parameters['steamId'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'The Steam id to release.' } /* #swagger.responses[200] = { description: 'Unlinked', content: { "application/json": { schema: { type: "object", properties: { unlinked: { type: "boolean", example: true } } } } } } */ /* #swagger.responses[404] = { description: 'Not linked to this user', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), param('steamId').matches(STEAM_ID_RE), validate, usersRust.removeLink, ) module.exports = usersRustRouter