const express = require('express') const path = require('path') const fs = require('fs') const multer = require('multer') const { body, param } = require('express-validator') const ctrl = require('./admin.controller') const { isLoggedIn } = require('../../../utils/auth') const noindex = require('../../../middleware/noindex') const validate = require('../../../middleware/validate') const adminRouter = express.Router() // Every admin route requires auth and is kept out of search indexes. adminRouter.use(noindex, isLoggedIn) // ── Image uploads (screenshots/gallery) ─────────────────────────────── const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(__dirname, '..', '..', '..', '..', 'uploads') fs.mkdirSync(UPLOAD_DIR, { recursive: true }) const storage = multer.diskStorage({ destination: (req, file, cb) => cb(null, UPLOAD_DIR), filename: (req, file, cb) => { const ext = path.extname(file.originalname).toLowerCase() cb(null, `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`) }, }) const upload = multer({ storage, limits: { fileSize: 8 * 1024 * 1024 }, fileFilter: (req, file, cb) => { if (/^image\/(png|jpe?g|gif|webp|avif)$/.test(file.mimetype)) cb(null, true) else cb(new Error('Only image uploads are allowed')) }, }) // ── Dashboard & site mode ───────────────────────────────────────────── adminRouter.get('/dashboard', ctrl.dashboard) adminRouter.put( '/site-mode', body('mode').isIn(['live', 'maintenance']), validate, ctrl.setSiteMode, ) // ── Posts (news / five-on-friday / newsletter / screenshots) ────────── adminRouter.get('/posts', ctrl.listPosts) adminRouter.post( '/posts', body('category').isString().notEmpty(), body('title').isString().trim().notEmpty().isLength({ max: 200 }), validate, ctrl.createPost, ) adminRouter.post('/posts/upload', upload.single('image'), ctrl.uploadImage) // Generalized upload (rich-text editors). Same multer middleware; returns { url }. adminRouter.post('/uploads', upload.single('image'), ctrl.uploadFile) adminRouter.get('/posts/:id', param('id').isInt(), validate, ctrl.getPost) adminRouter.put('/posts/:id', param('id').isInt(), validate, ctrl.updatePost) adminRouter.patch( '/posts/:id/publish', param('id').isInt(), body('published').isBoolean(), validate, ctrl.publishPost, ) adminRouter.delete('/posts/:id', param('id').isInt(), validate, ctrl.deletePost) // ── Wiki categories (static paths registered before /wiki/:slug) ─────── adminRouter.get('/wiki/categories', ctrl.listWikiCategories) adminRouter.post( '/wiki/categories', body('slug').matches(/^[a-z0-9-]+$/), body('title').isString().trim().notEmpty().isLength({ max: 200 }), body('description').optional({ values: 'falsy' }).isString().isLength({ max: 400 }), body('sort_order').optional().isInt(), validate, ctrl.createWikiCategory, ) adminRouter.put( '/wiki/categories/:id', param('id').isInt(), body('slug').optional().matches(/^[a-z0-9-]+$/), body('title').optional().isString().trim().notEmpty().isLength({ max: 200 }), body('description').optional({ values: 'falsy' }).isString().isLength({ max: 400 }), body('sort_order').optional().isInt(), validate, ctrl.updateWikiCategory, ) adminRouter.delete('/wiki/categories/:id', param('id').isInt(), validate, ctrl.deleteWikiCategory) // ── Wiki tags ────────────────────────────────────────────────────────── adminRouter.get('/wiki/tags', ctrl.listWikiTags) // ── Wiki pages ───────────────────────────────────────────────────────── adminRouter.get('/wiki', ctrl.listWiki) adminRouter.post( '/wiki', body('slug').matches(/^[a-z0-9-]+$/), body('title').isString().trim().notEmpty().isLength({ max: 200 }), body('excerpt').optional({ values: 'falsy' }).isString().isLength({ max: 400 }), body('category_id').optional({ values: 'null' }).isInt(), body('published').optional().isBoolean(), body('tags').optional().isArray(), validate, ctrl.createWiki, ) adminRouter.get('/wiki/:slug', ctrl.getWiki) adminRouter.put( '/wiki/:slug', body('title').optional().isString().trim().notEmpty().isLength({ max: 200 }), body('excerpt').optional({ values: 'falsy' }).isString().isLength({ max: 400 }), body('category_id').optional({ values: 'null' }).isInt(), body('published').optional().isBoolean(), body('tags').optional().isArray(), body('change_note').optional({ values: 'falsy' }).isString().isLength({ max: 280 }), validate, ctrl.updateWiki, ) adminRouter.patch( '/wiki/:slug/publish', body('published').isBoolean(), validate, ctrl.publishWiki, ) adminRouter.get('/wiki/:slug/revisions', ctrl.listWikiRevisions) adminRouter.get('/wiki/:slug/revisions/:id', param('id').isInt(), validate, ctrl.getWikiRevision) adminRouter.post( '/wiki/:slug/revisions/:id/restore', param('id').isInt(), validate, ctrl.restoreWikiRevision, ) adminRouter.delete('/wiki/:slug', ctrl.deleteWiki) // ── Settings ────────────────────────────────────────────────────────── adminRouter.get('/settings', ctrl.getSettings) adminRouter.put('/settings', ctrl.updateSettings) // ── Activity log ────────────────────────────────────────────────────── adminRouter.get('/activity', ctrl.listActivity) // ── User management ─────────────────────────────────────────────────── adminRouter.get('/users', ctrl.listUsers) adminRouter.post( '/users', body('username').isString().trim().isLength({ min: 3, max: 32 }), body('password').isString().isLength({ min: 8, max: 64 }), body('role').optional().isIn(['admin', 'editor']), validate, ctrl.createUser, ) adminRouter.put( '/users/:id', param('id').isInt(), body('password').optional().isString().isLength({ min: 8, max: 64 }), body('role').optional().isIn(['admin', 'editor']), validate, ctrl.updateUser, ) adminRouter.delete('/users/:id', param('id').isInt(), validate, ctrl.deleteUser) module.exports = adminRouter