Nye-TeeOff/frontend/src/app/api/admin/uploads/images/route.ts

106 lines
3.2 KiB
TypeScript

import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { randomUUID } from "node:crypto";
import sharp from "sharp";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
const MAX_UPLOAD_SIZE_BYTES = 20 * 1024 * 1024;
const ALLOWED_MIME_TYPES = new Set([
"image/avif",
"image/gif",
"image/jpeg",
"image/png",
"image/tiff",
"image/webp",
]);
export const runtime = "nodejs";
function resolveUploadFolder(value: FormDataEntryValue | null) {
const normalized = String(value || "articles").trim().toLowerCase();
return normalized === "facilities" ? "facilities" : "articles";
}
function sanitizeFilenameStem(filename: string) {
const stem = path.parse(filename).name;
const normalized = stem
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return normalized || "image";
}
export async function POST(request: Request) {
const cookieStore = await cookies();
if (!cookieStore.get("admin_session")) {
return NextResponse.json({ detail: "Admin-innlogging kreves" }, { status: 401 });
}
try {
const formData = await request.formData();
const file = formData.get("file");
const uploadFolder = resolveUploadFolder(formData.get("folder"));
if (!(file instanceof File)) {
return NextResponse.json({ detail: "Fant ingen bildefil i requesten." }, { status: 400 });
}
if (!ALLOWED_MIME_TYPES.has(file.type)) {
return NextResponse.json(
{ detail: "Filtypen støttes ikke. Bruk JPG, PNG, WebP, TIFF, GIF eller AVIF." },
{ status: 415 },
);
}
if (file.size > MAX_UPLOAD_SIZE_BYTES) {
return NextResponse.json(
{ detail: "Bildeopplasting er begrenset til 20 MB per fil." },
{ status: 413 },
);
}
const inputBuffer = Buffer.from(await file.arrayBuffer());
const image = sharp(inputBuffer, { animated: false }).rotate();
const metadata = await image.metadata();
if (!metadata.width || !metadata.height) {
return NextResponse.json({ detail: "Kunne ikke lese bildedimensjonene." }, { status: 400 });
}
const dateSegment = new Date().toISOString().slice(0, 10);
const safeName = sanitizeFilenameStem(file.name);
const filename = `${safeName}-${randomUUID()}.avif`;
const relativeDirectory = path.join("uploads", uploadFolder, dateSegment);
const absoluteDirectory = path.join(process.cwd(), "public", relativeDirectory);
const absolutePath = path.join(absoluteDirectory, filename);
await mkdir(absoluteDirectory, { recursive: true });
const outputBuffer = await image
.resize({
width: 2400,
withoutEnlargement: true,
})
.avif({
quality: 68,
effort: 6,
})
.toBuffer();
await writeFile(absolutePath, outputBuffer);
return NextResponse.json({
url: `/${relativeDirectory.replaceAll(path.sep, "/")}/${filename}`,
contentType: "image/avif",
width: metadata.width,
height: metadata.height,
});
} catch (error) {
console.error("Image upload failed", error);
return NextResponse.json({ detail: "Kunne ikke prosessere bildefilen." }, { status: 500 });
}
}