// Admin · Dashboard — the landing summary, plus the /site-mode singleton. // // Mounted at the ROOT of /api/v1/admin by admin/index.js (not at a prefix), // which already applied `noindex, isLoggedIn, staffOnly`. Two singleton URLs // that share a swagger tag and a screen but not a path segment live together // here rather than in two one-route files, which is what the target tree in // docs/website/API_V2_PLAN.md § Phase 2 calls for. // // A root mount is the one place the split's "always mount at a prefix" rule is // relaxed, and it is safe ONLY because this file declares no router-level // middleware: a bare `use(gate)` here would run for every request passing // through toward another mount and 403 an editor on an unrelated route. Keep // gates per-route in this file. // // GET /dashboard — stats overview, any staff role. // PUT /site-mode — live ↔ maintenance, admin only. // // Neither is the audit log (/activity) nor the bot-scoring state // (/bot-activity); those are separate capabilities that read alike. Handlers // still live in admin.controller.js; this re-wires routes, not logic. const express = require('express') const { body } = require('express-validator') const ctrl = require('./admin.controller') const { requireRole } = require('../../../utils/auth') const validate = require('../../../middleware/validate') const dashboardRouter = express.Router() const adminOnly = requireRole('admin') dashboardRouter.get( '/dashboard', // #swagger.tags = ['Admin · Dashboard'] // #swagger.summary = 'Dashboard summary counts' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.responses[200] = { description: 'Summary: site mode, last change, post/user counts and recent activity', content: { "application/json": { schema: { type: "object", properties: { site_mode: { type: "string", example: "live" }, last_change: { type: "object", properties: { at: { type: "string", nullable: true }, by: { type: "string", nullable: true } } }, counts: { type: "object", properties: { posts: { type: "object", additionalProperties: true }, users: { type: "integer" } } }, recent_activity: { type: "array", items: { type: "object", additionalProperties: true } } } } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ ctrl.dashboard, ) dashboardRouter.put( '/site-mode', // #swagger.tags = ['Admin · Dashboard'] // #swagger.summary = 'Set site mode (admin only)' // #swagger.description = 'Switch the site between live and maintenance.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/SiteModeRequest" } } } } */ /* #swagger.responses[200] = { description: 'Updated site mode', content: { "application/json": { schema: { $ref: "#/components/schemas/SiteModeState" } } } } */ /* #swagger.responses[400] = { description: 'Validation error', 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('mode').isIn(['live', 'maintenance']), validate, ctrl.setSiteMode, ) module.exports = dashboardRouter