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 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"); 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", "articles", 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 }); } }