import { NextRequest, NextResponse } from "next/server"; import { writeFile, mkdir } from "fs/promises"; import { existsSync } from "fs"; import path from "path"; import db from "@/lib/db"; import { auditLog } from "@/lib/middleware/auditLog"; const UPLOAD_DIR = path.join(process.cwd(), "public", "uploads"); const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB const ALLOWED_TYPES = [ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "image/jpeg", "image/png", "image/gif", "text/plain" ]; export async function POST(request) { try { const formData = await request.formData(); const file = formData.get("file"); const entityType = formData.get("entityType"); const entityId = formData.get("entityId"); const description = formData.get("description") || ""; if (!file || !entityType || !entityId) { return NextResponse.json( { error: "File, entityType, and entityId are required" }, { status: 400 } ); } // Validate entity type if (!["contract", "project", "task"].includes(entityType)) { return NextResponse.json( { error: "Invalid entity type" }, { status: 400 } ); } // Validate file if (file.size > MAX_FILE_SIZE) { return NextResponse.json( { error: "File size too large (max 10MB)" }, { status: 400 } ); } if (!ALLOWED_TYPES.includes(file.type)) { return NextResponse.json( { error: "File type not allowed" }, { status: 400 } ); } // Create upload directory structure const entityDir = path.join(UPLOAD_DIR, entityType + "s", entityId); if (!existsSync(entityDir)) { await mkdir(entityDir, { recursive: true }); } // Generate unique filename const timestamp = Date.now(); const sanitizedOriginalName = file.name.replace(/[^a-zA-Z0-9.-]/g, "_"); const storedFilename = `${timestamp}_${sanitizedOriginalName}`; const filePath = path.join(entityDir, storedFilename); const relativePath = `/uploads/${entityType}s/${entityId}/${storedFilename}`; // Save file const bytes = await file.arrayBuffer(); const buffer = Buffer.from(bytes); await writeFile(filePath, buffer); // Save to database const stmt = db.prepare(` INSERT INTO file_attachments ( entity_type, entity_id, original_filename, stored_filename, file_path, file_size, mime_type, description, uploaded_by ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `); const result = stmt.run( entityType, parseInt(entityId), file.name, storedFilename, relativePath, file.size, file.type, description, null // TODO: Get from session when auth is implemented ); const newFile = { file_id: result.lastInsertRowid, entity_type: entityType, entity_id: parseInt(entityId), original_filename: file.name, stored_filename: storedFilename, file_path: relativePath, file_size: file.size, mime_type: file.type, description: description, upload_date: new Date().toISOString() }; return NextResponse.json(newFile, { status: 201 }); } catch (error) { console.error("File upload error:", error); return NextResponse.json( { error: "Failed to upload file" }, { status: 500 } ); } } export async function GET(request) { try { const { searchParams } = new URL(request.url); const entityType = searchParams.get("entityType"); const entityId = searchParams.get("entityId"); if (!entityType || !entityId) { return NextResponse.json( { error: "entityType and entityId are required" }, { status: 400 } ); } const files = db.prepare(` SELECT file_id, entity_type, entity_id, original_filename, stored_filename, file_path, file_size, mime_type, description, upload_date, uploaded_by FROM file_attachments WHERE entity_type = ? AND entity_id = ? ORDER BY upload_date DESC `).all(entityType, parseInt(entityId)); return NextResponse.json(files); } catch (error) { console.error("Error fetching files:", error); return NextResponse.json( { error: "Failed to fetch files" }, { status: 500 } ); } }