refactor(server): split admin moderation, bot-activity and activity into capability routers
PR 2 of the domain split (docs/website/API_V2_PLAN.md § Phase 2). Carves 18 more
routes out of admin.routes.js into one router file per business capability,
in place, with every URL unchanged:
moderation.router.js (15) /admin/moderation modAccess at router level
botActivity.router.js (2) /admin/bot-activity adminOnly per route
activity.router.js (1) /admin/activity staff-wide, no extra gate
The residual admin.routes.js drops from 82 routes to 64.
Moderation was already gated by a prefix mount (adminRouter.use('/moderation',
modAccess)), so moderationRouter.use(modAccess) is the exact equivalent now that
the router is mounted at a prefix. Bot-activity's adminOnly was per-route and is
deliberately kept per-route: that is what holds the per-route handler count in
routes.guards.json, the only signal that would catch a dropped gate, since
requireRole(...) returns an anonymous arrow and never appears by name.
/activity gets its own file rather than waiting for dashboard.router.js in PR 4
— it is the staff audit log, a different capability from the dashboard's stats
overview and from the botScore middleware's in-memory ban state.
Acceptance:
- routes.manifest.json zero-diff (200 public + 2 internal)
- routes.guards.json zero-diff
- swagger-output.json zero-diff (198 operations)
- api-route-inventory.json already in sync
- 434 server tests green
- role gates verified identical to main by reading the requireRole role sets
off the live Express stack for every moved route plus untouched controls
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
47
server/src/router/v1/admin/botActivity.router.js
Normal file
47
server/src/router/v1/admin/botActivity.router.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// Admin · Bot Activity — the botScore middleware's scoring/ban state.
|
||||
//
|
||||
// Mounted at /api/v1/admin/bot-activity by admin/index.js, which already applied
|
||||
// `noindex, isLoggedIn, staffOnly`. Read-only view of the in-memory scores,
|
||||
// banned IPs and recent events, plus an emergency unban for false positives.
|
||||
//
|
||||
// Admin-only, and kept as a per-route gate rather than a router-level `use` so
|
||||
// the middleware chain each route carries is unchanged by the move.
|
||||
|
||||
const express = require('express')
|
||||
const { body } = require('express-validator')
|
||||
|
||||
const botActivity = require('./botActivity.controller')
|
||||
const { requireRole } = require('../../../utils/auth')
|
||||
const validate = require('../../../middleware/validate')
|
||||
|
||||
const botActivityRouter = express.Router()
|
||||
const adminOnly = requireRole('admin')
|
||||
|
||||
botActivityRouter.get(
|
||||
'/',
|
||||
// #swagger.tags = ['Admin · Bot Activity']
|
||||
// #swagger.summary = 'Bot-scoring / ban state and recent events (admin only)'
|
||||
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
|
||||
/* #swagger.responses[200] = { description: 'Banned IPs, scores and recent events', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */
|
||||
/* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
|
||||
/* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
|
||||
adminOnly,
|
||||
botActivity.getBotActivity,
|
||||
)
|
||||
botActivityRouter.post(
|
||||
'/unban',
|
||||
// #swagger.tags = ['Admin · Bot Activity']
|
||||
// #swagger.summary = 'Emergency unban an IP (admin only)'
|
||||
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
|
||||
/* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/UnbanRequest" } } } } */
|
||||
/* #swagger.responses[200] = { description: 'Unbanned (echoes the ip and whether an entry was cleared)', content: { "application/json": { schema: { $ref: "#/components/schemas/UnbanResult" } } } } */
|
||||
/* #swagger.responses[400] = { description: 'Invalid IP', content: { "application/json": { schema: { $ref: "#/components/schemas/ValidationError" } } } } */
|
||||
/* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
|
||||
/* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
|
||||
adminOnly,
|
||||
body('ip').isIP(),
|
||||
validate,
|
||||
botActivity.unbanIp,
|
||||
)
|
||||
|
||||
module.exports = botActivityRouter
|
||||
Reference in New Issue
Block a user