65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
|
|
/**
|
|||
|
|
* 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/<itemId>/<uuid>.<ext>
|
|||
|
|
* 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) });
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
);
|