106 lines
3.2 KiB
TypeScript
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 });
|
|
}
|
|
}
|