import { NextRequest, NextResponse } from "next/server"; import { writeFile, mkdir } from "fs/promises"; import { join } from "path"; import sharp from "sharp"; import { db } from "@/db"; import { images } from "@/db/schema"; import { nanoid } from "nanoid"; import { auth } from "@/app/lib/auth"; async function getUserId(request: NextRequest): Promise { try { const session = await auth.api.getSession({ headers: request.headers, }); return session?.user?.id || null; } catch { return null; } } function getBaseUrl(request: NextRequest): string { // First, check environment variables (production should set this) if (process.env.NEXT_PUBLIC_APP_URL) { return process.env.NEXT_PUBLIC_APP_URL; } if (process.env.APP_URL) { return process.env.APP_URL; } // Check for reverse proxy headers (X-Forwarded-Host, X-Forwarded-Proto) const forwardedHost = request.headers.get("x-forwarded-host"); const forwardedProto = request.headers.get("x-forwarded-proto"); if (forwardedHost && forwardedProto) { return `${forwardedProto}://${forwardedHost}`; } // Fallback to request origin return request.nextUrl.origin; } export async function POST(request: NextRequest) { try { const userId = await getUserId(request); if (!userId) { return NextResponse.json( { message: "Yetkisiz erişim" }, { status: 401 } ); } const formData = await request.formData(); const file = formData.get("file") as File; if (!file) { return NextResponse.json( { message: "Dosya bulunamadı" }, { status: 400 } ); } // File size validation (max 10MB) const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB if (file.size > MAX_FILE_SIZE) { return NextResponse.json( { message: "Dosya boyutu çok büyük. Maksimum 10MB olmalıdır." }, { status: 400 } ); } // File type validation const allowedMimeTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp", "image/avif"]; if (!allowedMimeTypes.includes(file.type)) { return NextResponse.json( { message: "Geçersiz dosya tipi. Sadece resim dosyaları kabul edilir." }, { status: 400 } ); } // Input validation const widthInput = formData.get("width") as string; const heightInput = formData.get("height") as string; const qualityInput = formData.get("quality") as string; const formatInput = (formData.get("format") as string) || "jpeg"; const width = Math.max(1, Math.min(10000, parseInt(widthInput) || 800)); const height = Math.max(1, Math.min(10000, parseInt(heightInput) || 600)); const quality = Math.max(1, Math.min(100, parseInt(qualityInput) || 90)); const allowedFormats = ["jpeg", "jpg", "png", "webp", "avif"]; const format = allowedFormats.includes(formatInput) ? formatInput : "jpeg"; const bytes = await file.arrayBuffer(); const buffer = Buffer.from(bytes); // Resim manipülasyonu - Tam istenen boyuta getir (crop ile, bozmadan) // fit: "cover" kullanarak resmi tam boyuta getiriyoruz // Aspect ratio korunur, fazla kısımlar ortadan kesilir (crop) let processedBuffer = sharp(buffer).resize(width, height, { fit: "cover", // Tam boyuta getir, aspect ratio koru, fazla kısımları kes position: "center", // Ortadan crop yap withoutEnlargement: false, // Gerekirse büyüt de tam boyuta getir }); // Format ve kalite ayarları const normalizedFormat = format === "jpg" ? "jpeg" : format; if (normalizedFormat === "jpeg") { processedBuffer = processedBuffer.jpeg({ quality }); } else if (normalizedFormat === "png") { processedBuffer = processedBuffer.png({ quality }); } else if (normalizedFormat === "webp") { processedBuffer = processedBuffer.webp({ quality }); } else if (normalizedFormat === "avif") { processedBuffer = processedBuffer.avif({ quality }); } const processedImage = await processedBuffer.toBuffer(); const metadata = await sharp(processedImage).metadata(); // Dosya adı oluştur const fileId = nanoid(); const originalName = file.name; const fileExtension = normalizedFormat === "jpeg" ? "jpg" : normalizedFormat; const fileName = `${fileId}.${fileExtension}`; // Uploads klasörünü oluştur // Docker volume mount: /app/public/uploads // Standalone build'de process.cwd() = /app olmalı const uploadsDir = join(process.cwd(), "public", "uploads"); try { await mkdir(uploadsDir, { recursive: true }); } catch (mkdirError: any) { console.error("Uploads klasörü oluşturulamadı:", mkdirError); console.error("Klasör yolu:", uploadsDir); console.error("Current working directory:", process.cwd()); throw new Error(`Uploads klasörü oluşturulamadı: ${mkdirError.message}`); } // Dosyayı kaydet const filePath = join(uploadsDir, fileName); try { await writeFile(filePath, processedImage); } catch (writeError: any) { console.error("Dosya yazılamadı:", writeError); console.error("Dosya yolu:", filePath); console.error("Dosya boyutu:", processedImage.length); throw new Error(`Dosya yazılamadı: ${writeError.message}`); } // Veritabanına kaydet const imageUrl = `/uploads/${fileName}`; const imageId = nanoid(); await db.insert(images).values({ id: imageId, userId, originalName, fileName, filePath: filePath, url: imageUrl, width: metadata.width || null, height: metadata.height || null, quality, format: normalizedFormat, fileSize: processedImage.length, }); // Get base URL const baseUrl = getBaseUrl(request); const fullImageUrl = `${baseUrl}${imageUrl}`; return NextResponse.json({ message: "Resim başarıyla yüklendi", image: { id: imageId, url: fullImageUrl, width: metadata.width, height: metadata.height, }, }); } catch (error: any) { console.error("Upload hatası:", error); console.error("Error stack:", error?.stack); console.error("Error message:", error?.message); // Production'da detaylı hata mesajı döndür (debug için) const errorMessage = process.env.NODE_ENV === "production" ? `Yükleme başarısız: ${error?.message || "Bilinmeyen hata"}` : "Yükleme başarısız"; return NextResponse.json( { message: errorMessage }, { status: 500 } ); } }