Files
website/server/src/router/v1/admin/uploads.router.js
wtclaude 00ad16858a
All checks were successful
PR Checks / bot-install (pull_request) Successful in 16s
PR Checks / server-tests (pull_request) Successful in 37s
PR Checks / client-build (pull_request) Successful in 9m15s
refactor(server): split admin posts, uploads, wiki and pages into capability routers
PR 3 of the in-place admin router split (docs/website/API_V2_PLAN.md § Phase 2).
Moves the content tier out of the residual admin.routes.js into one router file
per capability, each mounted at the prefix it already owned. No URL, gate or
handler changes.

  posts.router.js     ( 9)  /admin/posts
  uploads.router.js   ( 1)  /admin/uploads
  wiki.router.js      (14)  /admin/wiki
  pages.router.js     ( 7)  /admin/pages
  admin.routes.js     (33)  residual, was 64

All four capabilities are editor tier, so no gate moved: the shared
`noindex, isLoggedIn, staffOnly` in admin/index.js is their whole gate.

The multer config moved to admin/imageUpload.js because the two routes that
share it (POST /posts/upload and POST /uploads) now live in different files;
duplicating a mimetype allowlist is how the two copies drift. It stays in
admin/ because UPLOAD_DIR is resolved relative to __dirname.

Acceptance — all four gates zero-diff:
  routes.manifest.json    unchanged (200 public + 2 internal)
  routes.guards.json      unchanged (no route lost or gained a gate)
  swagger-output.json     unchanged (198 operations)
  api-route-inventory.json already in sync
plus 434 server tests green.

Verified separately, because no gate can catch it: the wiki router's literal
/categories and /tags paths still precede /:slug in declaration order. The
manifest sorts its entries, so a reordering there would be invisible.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 19:24:07 -05:00

34 lines
1.8 KiB
JavaScript

// Admin · Uploads — the generalized image upload used by the rich-text editors
// (wiki, CMS pages). Returns { url }, where the posts-specific sibling
// POST /admin/posts/upload returns { image_url }; both write to the same
// directory through the shared multer config in imageUpload.js.
//
// Mounted at /api/v1/admin/uploads by admin/index.js, which already applied
// `noindex, isLoggedIn, staffOnly`. No extra gate — same editor tier as posts.
//
// The swagger tag stays 'Admin · Posts', matching the committed spec. Retagging
// it would be a real OpenAPI diff, not a route move, so it does not belong in a
// split PR whose acceptance criterion is a byte-identical spec.
const express = require('express')
const ctrl = require('./admin.controller')
const { upload } = require('./imageUpload')
const uploadsRouter = express.Router()
uploadsRouter.post(
'/',
// #swagger.tags = ['Admin · Posts']
// #swagger.summary = 'Upload an image for rich-text editors (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 file URL', content: { "application/json": { schema: { $ref: "#/components/schemas/UploadResponse" } } } } */
/* #swagger.responses[400] = { description: 'No file / 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.uploadFile,
)
module.exports = uploadsRouter