53 lines
3.0 KiB
JavaScript
53 lines
3.0 KiB
JavaScript
/**
|
|
* @file Verarbeitet das Hochladen, Abrufen und Entfernen von Bildanhängen mit Zugriffskontrolle.
|
|
*/
|
|
import express from "express";
|
|
import {mkdirSync} from "node:fs";
|
|
import {unlink} from "node:fs/promises";
|
|
import {asyncHandler} from "../middleware/asyncHandler.js";
|
|
import multer from "multer";
|
|
import {requireLogin} from "../middleware/auth.js";
|
|
import {AttachmentError} from "../services/attachmentService.js";
|
|
|
|
export function createUploadsRouter(service, config) {
|
|
const router = express.Router();
|
|
mkdirSync(config.uploadTmpDir, {recursive: true});
|
|
const activeUploads = new Map();
|
|
const upload = multer({storage: multer.diskStorage({destination: config.uploadTmpDir}), limits: {
|
|
fileSize: config.uploadMaxSizeMb * 1024 * 1024, files: 1, fields: 0, parts: 1
|
|
}}).single("image");
|
|
const fail = (res, error) => res.status(error instanceof AttachmentError ? error.status : 500).json({error:
|
|
error instanceof AttachmentError ? error.message : "Image operation failed. Please retry."});
|
|
router.get("/uploads/config", requireLogin, (_req, res) => res.json({maxSizeMb: config.uploadMaxSizeMb, maxFiles: config.uploadMaxFiles}));
|
|
router.post("/uploads", requireLogin, (req, res) => {
|
|
const user = req.session.user;
|
|
const active = activeUploads.get(user) || 0;
|
|
if (active >= config.uploadMaxFiles) return res.status(429).json({error: "Too many simultaneous uploads. Please wait."});
|
|
activeUploads.set(user, active + 1);
|
|
res.once("close", () => {
|
|
const remaining = (activeUploads.get(user) || 1) - 1;
|
|
if (remaining) activeUploads.set(user, remaining); else activeUploads.delete(user);
|
|
});
|
|
upload(req, res, async error => {
|
|
if (error) { if (req.file?.path) await unlink(req.file.path).catch(() => {}); return res.status(error.code === "LIMIT_FILE_SIZE" ? 413 : 400).json({error:
|
|
error.code === "LIMIT_FILE_SIZE" ? `Image exceeds ${config.uploadMaxSizeMb} MB` : "Upload one PNG, JPEG or WebP image per request"});
|
|
}
|
|
try { res.status(201).json(await service.upload(req.file, req.session.user)); }
|
|
catch (failure) { fail(res, failure); }
|
|
});
|
|
});
|
|
router.get("/attachments/:id", requireLogin, asyncHandler(async (req, res) => {
|
|
try {
|
|
const row = service.owned(req.params.id, req.session.user);
|
|
const buffer = await service.storage.read(row.storage_key);
|
|
res.set({"Content-Type": row.mime_type, "Content-Length": buffer.length, "Cache-Control": "private, no-store",
|
|
"X-Content-Type-Options": "nosniff", "Content-Disposition": "inline"}).send(buffer);
|
|
} catch (error) { fail(res, error); }
|
|
}));
|
|
router.delete("/attachments/:id", requireLogin, asyncHandler(async (req, res) => {
|
|
try { await service.remove(req.params.id, req.session.user); res.json({success: true}); }
|
|
catch (error) { fail(res, error); }
|
|
}));
|
|
return router;
|
|
}
|