fix: upload permissions in Docker and improve file validation

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-06-06 15:53:16 +03:00
parent 5c5aa6caec
commit 43f477ead9
4 changed files with 81 additions and 18 deletions
+62 -14
View File
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { writeFile, mkdir } from "fs/promises";
import { writeFile, mkdir, access } from "fs/promises";
import { constants } from "fs";
import path from "path";
import { prisma } from "@/lib/prisma";
@@ -10,7 +11,44 @@ const ALLOWED_TYPES = [
"image/webp",
"image/svg+xml",
];
const MIME_BY_EXT: Record<string, string> = {
jpg: "image/jpeg",
jpeg: "image/jpeg",
png: "image/png",
gif: "image/gif",
webp: "image/webp",
svg: "image/svg+xml",
};
const MAX_SIZE = 10 * 1024 * 1024;
const UPLOAD_DIR = path.join(process.cwd(), "public", "uploads");
function resolveMimeType(file: File): string | null {
if (file.type && ALLOWED_TYPES.includes(file.type)) {
return file.type;
}
const ext = file.name.split(".").pop()?.toLowerCase() || "";
return MIME_BY_EXT[ext] ?? null;
}
function safeExtension(filename: string, mimeType: string): string {
const ext = filename.split(".").pop()?.toLowerCase();
if (ext && MIME_BY_EXT[ext]) {
return ext;
}
const byMime: Record<string, string> = {
"image/jpeg": "jpg",
"image/png": "png",
"image/gif": "gif",
"image/webp": "webp",
"image/svg+xml": "svg",
};
return byMime[mimeType] || "jpg";
}
export async function POST(request: NextRequest) {
try {
@@ -18,13 +56,11 @@ export async function POST(request: NextRequest) {
const file = formData.get("file") as File | null;
if (!file) {
return NextResponse.json(
{ error: "Файл не найден" },
{ status: 400 }
);
return NextResponse.json({ error: "Файл не найден" }, { status: 400 });
}
if (!ALLOWED_TYPES.includes(file.type)) {
const mimeType = resolveMimeType(file);
if (!mimeType) {
return NextResponse.json(
{ error: "Недопустимый тип файла" },
{ status: 400 }
@@ -38,15 +74,24 @@ export async function POST(request: NextRequest) {
);
}
const ext = file.name.split(".").pop()?.toLowerCase() || "jpg";
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}.${ext}`;
const uploadDir = path.join(process.cwd(), "public", "uploads");
await mkdir(UPLOAD_DIR, { recursive: true });
await mkdir(uploadDir, { recursive: true });
try {
await access(UPLOAD_DIR, constants.W_OK);
} catch {
console.error("Upload dir not writable:", UPLOAD_DIR);
return NextResponse.json(
{ error: "Каталог загрузок недоступен для записи" },
{ status: 500 }
);
}
const ext = safeExtension(file.name, mimeType);
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}.${ext}`;
const filepath = path.join(UPLOAD_DIR, filename);
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
await writeFile(path.join(uploadDir, filename), buffer);
await writeFile(filepath, Buffer.from(bytes));
const url = `/uploads/${filename}`;
@@ -54,13 +99,16 @@ export async function POST(request: NextRequest) {
data: {
filename,
originalName: file.name,
mimeType: file.type,
mimeType,
size: file.size,
url,
},
});
return NextResponse.json(photo);
return NextResponse.json({
...photo,
createdAt: photo.createdAt.toISOString(),
});
} catch (error) {
console.error("Upload error:", error);
return NextResponse.json(