Files
mini-game-ai/src/app/api/process/image-compress/route.ts

139 lines
3.6 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { readFile, readdir } from "fs/promises";
import path from "path";
import type { ImageCompressConfig } from "@/types";
import {
saveProcessedFile,
cleanupFile,
sanitizeFilename,
} from "@/lib/file-storage";
import {
compressImage,
getImageMetadata,
validateImageBuffer,
validateCompressConfig,
} from "@/lib/image-processor";
export const runtime = "nodejs";
const UPLOAD_DIR = process.env.TEMP_DIR || path.join(process.cwd(), ".temp", "uploads");
interface ProcessRequest {
fileId: string;
config: ImageCompressConfig;
}
/**
* Find file by ID in upload directory
*/
async function findUploadedFile(fileId: string): Promise<{ buffer: Buffer; name: string } | null> {
try {
const files = await readdir(UPLOAD_DIR);
const file = files.find((f) => f.startsWith(`${fileId}.`));
if (!file) {
return null;
}
const filePath = path.join(UPLOAD_DIR, file);
const buffer = await readFile(filePath);
// Validate it's actually an image
const isValid = await validateImageBuffer(buffer);
if (!isValid) {
return null;
}
// Extract original name from file (remove UUID prefix)
const originalName = file.substring(fileId.length + 1);
return { buffer, name: originalName };
} catch {
return null;
}
}
export async function POST(request: NextRequest) {
try {
const body: ProcessRequest = await request.json();
const { fileId, config } = body;
// Validate request
if (!fileId || typeof fileId !== "string") {
return NextResponse.json(
{ success: false, error: "Valid file ID is required" },
{ status: 400 }
);
}
// Sanitize file ID to prevent path traversal
const sanitizedId = sanitizeFilename(fileId).replace(/\.[^.]+$/, "");
if (sanitizedId !== fileId) {
return NextResponse.json(
{ success: false, error: "Invalid file ID" },
{ status: 400 }
);
}
// Validate config
const configValidation = validateCompressConfig(config);
if (!configValidation.valid) {
return NextResponse.json(
{ success: false, error: configValidation.error },
{ status: 400 }
);
}
// Find uploaded file
const uploadedFile = await findUploadedFile(fileId);
if (!uploadedFile) {
return NextResponse.json(
{ success: false, error: "File not found or expired" },
{ status: 404 }
);
}
// Get original metadata
const originalMetadata = await getImageMetadata(uploadedFile.buffer);
// Process image
const result = await compressImage(uploadedFile.buffer, config);
// Save processed file
const outputFormat = config.format === "original" ? originalMetadata.format : config.format;
const downloadInfo = await saveProcessedFile(
fileId, // Original file ID for tracking
result.buffer,
outputFormat,
uploadedFile.name
);
// Cleanup original file
await cleanupFile(fileId);
return NextResponse.json({
success: true,
fileUrl: downloadInfo.fileUrl,
filename: downloadInfo.filename,
metadata: {
format: result.format,
quality: config.quality,
compressionRatio: result.compressionRatio,
originalSize: result.originalSize,
compressedSize: result.compressedSize,
width: result.width,
height: result.height,
},
});
} catch (error) {
console.error("Processing error:", error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "Processing failed",
},
{ status: 500 }
);
}
}