// ── The `admin.users.detail` extension slot's contents ───────────────────── // // MODULE-UO CONTENT, still living in core. MODULE_SYSTEM.md §1.9 named the // fourth mount shape: module routes hanging off a CORE resource. These six paths // are shard reads on `/admin/users/:id`, a user-management URL core owns, so // they cannot move with a prefix and cannot stay where they are either. // // The resolution is an extension SLOT. `users.router.js` declares // `admin.users.detail` and mounts its router at `/:id`; this file is what fills // it, registered through modules/registries.js like a module would // (registerCore() → `api.registerExtension('admin.users.detail', …)`). Phase 3 // moves this file to module-uo and changes nothing else — the six URLs are // identical either way, and core never learns what "shard" means. // // `mergeParams` comes from the slot's router, so `req.params.id` is the parent's // user id. Core's own routes on the resource are declared BEFORE the slot is // mounted, so core always wins a path conflict (MODULE_API.md §2.4). const express = require('express') const { param } = require('express-validator') const usersShard = require('./usersShard.controller') const validate = require('../../../middleware/validate') // Same shape the shard routes validate account names with. const SHARD_ACCOUNT_RE = /^[A-Za-z0-9_.-]{1,120}$/ const shardRouter = express.Router({ mergeParams: true }) // Backs the /admin/users/:id detail page: a user's linked game accounts and, // scoped to those accounts, their vendor sales / houses / online characters. // Live character rosters are fetched by the client through /admin/shard/* (which // already grants admins a bypass to any account), so no routes for them here. shardRouter.get( '/shard/accounts', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'A user’s linked game accounts (admin only)' // #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: { type: "array", items: { $ref: "#/components/schemas/ShardLink" } } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, usersShard.listAccounts, ) shardRouter.get( '/shard/sales', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Recent vendor sales on a user’s accounts (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'Vendor sales', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/ShardVendorSale" } } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, usersShard.getSales, ) shardRouter.get( '/shard/houses', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Houses owned by a user’s accounts (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'Houses (IDOC first)', content: { "application/json": { schema: { type: "array", items: { type: "object", additionalProperties: true } } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, usersShard.getHouses, ) shardRouter.get( '/shard/online', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'A user’s characters currently online (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'Online characters', content: { "application/json": { schema: { type: "array", items: { type: "object", additionalProperties: true } } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, usersShard.getOnline, ) shardRouter.get( '/shard/standing', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'A user’s shard standing — governorships held and guilds led (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'Standing { governorOf, guildsLed }', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, usersShard.getStanding, ) shardRouter.delete( '/shard/link/:account', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Unlink a game account from this user (admin only)' // #swagger.description = 'Severs a game account’s tie to the website user from the site side (sidecar DELETE /link/{account}) and drops the local mirror. actor is stamped from the session.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } // #swagger.parameters['account'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'Game account to unlink.' } /* #swagger.responses[200] = { description: 'Unlinked', content: { "application/json": { schema: { type: "object", properties: { account: { type: "string" }, unlinked: { type: "boolean" } } } } } } */ /* #swagger.responses[403] = { description: 'Protected staff account (refused by shard)', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not linked', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), param('account').matches(SHARD_ACCOUNT_RE), validate, usersShard.unlinkAccount, ) module.exports = shardRouter