// Admin · Posts — news, five-on-friday, newsletter and screenshot posts, plus // the announcement pipeline (town crier + Discord) status and retry. // // Mounted at /api/v1/admin/posts by admin/index.js, which already applied // `noindex, isLoggedIn, staffOnly`. No extra gate: managing content is the // editor tier's whole job, so admin, editor and moderator all reach these. // // Handlers still live in admin.controller.js; this PR re-wires routes, not logic. const express = require('express') const { body, param } = require('express-validator') const ctrl = require('./admin.controller') const { upload } = require('./imageUpload') const validate = require('../../../middleware/validate') const postsRouter = express.Router() postsRouter.get( '/', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'List all posts (including unpublished)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['category'] = { in: 'query', required: false, schema: { type: 'string' }, description: 'Optional category filter.' } /* #swagger.responses[200] = { description: 'Posts', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Post" } } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ ctrl.listPosts, ) postsRouter.post( '/', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'Create a post' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/PostCreateRequest" } } } } */ /* #swagger.responses[201] = { description: 'Created post', content: { "application/json": { schema: { $ref: "#/components/schemas/Post" } } } } */ /* #swagger.responses[400] = { description: 'Validation error or unknown category', content: { "application/json": { schema: { $ref: "#/components/schemas/ValidationError" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ body('category').isString().notEmpty(), body('title').isString().trim().notEmpty().isLength({ max: 200 }), validate, ctrl.createPost, ) postsRouter.post( '/upload', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'Upload a post image (multipart)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.requestBody = { required: true, content: { "multipart/form-data": { schema: { type: "object", properties: { image: { type: "string", format: "binary" } } } } } } */ /* #swagger.responses[201] = { description: 'Stored image URL', content: { "application/json": { schema: { type: "object", properties: { image_url: { type: "string", example: "/uploads/1700000000-abcd.png" } } } } } } */ /* #swagger.responses[400] = { description: 'No image / disallowed type', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ upload.single('image'), ctrl.uploadImage, ) postsRouter.get( '/:id', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'Get a post by id' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'Post id.' } /* #swagger.responses[200] = { description: 'The post', content: { "application/json": { schema: { $ref: "#/components/schemas/Post" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.getPost, ) postsRouter.put( '/:id', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'Update a post' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'Post id.' } /* #swagger.requestBody = { content: { "application/json": { schema: { $ref: "#/components/schemas/PostCreateRequest" } } } } */ /* #swagger.responses[200] = { description: 'Updated post', content: { "application/json": { schema: { $ref: "#/components/schemas/Post" } } } } */ /* #swagger.responses[400] = { description: 'Validation error or unknown category', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.updatePost, ) postsRouter.patch( '/:id/publish', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'Publish / unpublish a post' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'Post id.' } /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/PublishRequest" } } } } */ /* #swagger.responses[200] = { description: 'Updated post', content: { "application/json": { schema: { $ref: "#/components/schemas/Post" } } } } */ /* #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[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), body('published').isBoolean(), validate, ctrl.publishPost, ) postsRouter.delete( '/:id', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'Delete a post' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'Post id.' } /* #swagger.responses[200] = { description: 'Deleted (echoes the id)', content: { "application/json": { schema: { $ref: "#/components/schemas/DeletedId" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.deletePost, ) postsRouter.get( '/:id/announce', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'Get the announcement pipeline status for a post' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'Post id.' } /* #swagger.responses[200] = { description: 'The announce job for the post, or null if never announced', content: { "application/json": { schema: { type: "object", nullable: true, additionalProperties: true } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.getAnnounceStatus, ) postsRouter.post( '/:id/announce/retry', // #swagger.tags = ['Admin · Posts'] // #swagger.summary = 'Retry one announcement delivery leg (town crier or Discord)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'Post id.' } /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { type: "object", properties: { leg: { type: "string", enum: ["towncrier", "discord"] } }, required: ["leg"] } } } } */ /* #swagger.responses[200] = { description: 'Updated announce job', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */ /* #swagger.responses[404] = { description: 'No announcement job for this post', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), body('leg').isIn(['towncrier', 'discord']), validate, ctrl.retryAnnounceLeg, ) module.exports = postsRouter