/** * Document Upload API Endpoint * Agent 3: File Upload & Storage System * * POST /api/upload/document - Upload a document file (PDF, DOC, etc.) */ import type { NextApiRequest, NextApiResponse } from 'next'; import { getUploadService } from '../../../lib/storage'; import type { FileCategory } from '../../../lib/storage/types'; // Disable body parser to handle multipart/form-data export const config = { api: { bodyParser: false, }, }; interface UploadResponse { success: boolean; file?: { id: string; url: string; size: number; mimeType: string; originalName: string; }; error?: string; } async function parseMultipartForm( req: NextApiRequest ): Promise<{ buffer: Buffer; filename: string; mimeType: string; fields: Record }> { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; let filename = 'document'; let mimeType = 'application/octet-stream'; const fields: Record = {}; const contentType = req.headers['content-type'] || ''; const boundary = contentType.split('boundary=')[1]; if (!boundary) { reject(new Error('Missing multipart boundary')); return; } req.on('data', (chunk: Buffer) => { chunks.push(chunk); }); req.on('end', () => { const buffer = Buffer.concat(chunks); const content = buffer.toString('binary'); const parts = content.split(`--${boundary}`); for (const part of parts) { if (part.includes('Content-Disposition: form-data')) { const nameMatch = part.match(/name="([^"]+)"/); const filenameMatch = part.match(/filename="([^"]+)"/); if (filenameMatch) { filename = filenameMatch[1]; const contentTypeMatch = part.match(/Content-Type: ([^\r\n]+)/); if (contentTypeMatch) { mimeType = contentTypeMatch[1].trim(); } const contentStart = part.indexOf('\r\n\r\n') + 4; const contentEnd = part.lastIndexOf('\r\n'); if (contentStart > 4 && contentEnd > contentStart) { const fileContent = part.slice(contentStart, contentEnd); const fileBuffer = Buffer.from(fileContent, 'binary'); resolve({ buffer: fileBuffer, filename, mimeType, fields }); return; } } else if (nameMatch) { const fieldName = nameMatch[1]; const contentStart = part.indexOf('\r\n\r\n') + 4; const contentEnd = part.lastIndexOf('\r\n'); if (contentStart > 4 && contentEnd > contentStart) { fields[fieldName] = part.slice(contentStart, contentEnd).trim(); } } } } reject(new Error('No file found in request')); }); req.on('error', reject); }); } export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method !== 'POST') { return res.status(405).json({ success: false, error: 'Method not allowed' }); } try { const { buffer, filename, mimeType, fields } = await parseMultipartForm(req); // Validate document type const allowedTypes = [ 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'image/jpeg', 'image/png', ]; if (!allowedTypes.includes(mimeType)) { return res.status(400).json({ success: false, error: 'Invalid file type. Only PDF, DOC, DOCX, and images are allowed.', }); } const category = (fields.category as FileCategory) || 'document'; const plantId = fields.plantId; const farmId = fields.farmId; const userId = fields.userId; const uploadService = getUploadService(); const result = await uploadService.upload(buffer, filename, mimeType, { category, plantId, farmId, userId, generateThumbnails: false, // No thumbnails for documents }); if (!result.success || !result.file) { return res.status(400).json({ success: false, error: result.error || 'Upload failed', }); } return res.status(200).json({ success: true, file: { id: result.file.id, url: result.file.url, size: result.file.size, mimeType: result.file.mimeType, originalName: result.file.originalName, }, }); } catch (error) { console.error('Document upload error:', error); return res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Internal server error', }); } }