/** * POST /api/media/upload – multipart upload; saves to local disk * DELETE /api/media/:key – delete a file by key (admin/event_manager) * * Upload flow (replaces the old presigned-URL pattern): * 1. Client POSTs multipart/form-data with fields: itemId, mediaType, plus the file * 2. Server saves to UPLOAD_DIR/items//. * 3. Server returns { url, key, mimetype, sizeBytes } * 4. Client calls POST /api/items/:id/media with { mediaType, url } to attach the * record to the item (existing endpoint in routes/items.ts) * * Files are served as static assets at /media/* (see app.ts). * Everything stays on the local machine — no internet required. */ import { Router } from "express"; import { requireAuth, requireRole } from "../middleware/auth.js"; import { upload, resolveFile, deleteFile, type MediaType } from "../services/storage.js"; export const mediaRouter = Router(); const STAFF_WRITE = requireRole("admin", "event_manager"); // ── Upload ───────────────────────────────────────────────────────────────────── mediaRouter.post( "/upload", requireAuth, STAFF_WRITE, // Parse a single file field named "file" plus any text fields (itemId, mediaType) upload.single("file"), (req, res) => { if (!req.file) { res.status(400).json({ error: "No file received" }); return; } const mediaType = (req.body as { mediaType?: string }).mediaType as MediaType | undefined; if (!mediaType || !["image", "video", "document"].includes(mediaType)) { res.status(400).json({ error: "mediaType must be image, video, or document" }); return; } try { const saved = resolveFile(req.file, mediaType); res.status(201).json(saved); } catch (err) { res.status(400).json({ error: String(err) }); } }, ); // ── Delete ───────────────────────────────────────────────────────────────────── mediaRouter.delete( "/:key(*)", // key contains slashes, e.g. items/abc/uuid.jpg requireAuth, STAFF_WRITE, async (req, res) => { try { await deleteFile(req.params["key"] ?? ""); res.json({ ok: true }); } catch (err) { res.status(400).json({ error: String(err) }); } }, );