port-nimara-client-portal/server/api/files/upload.ts

120 lines
3.7 KiB
TypeScript

import { uploadFile, getMinioClient } from '~/server/utils/minio';
import formidable from 'formidable';
import { promises as fs } from 'fs';
import mime from 'mime-types';
export default defineEventHandler(async (event) => {
try {
// Get the current path and bucket from query params
const query = getQuery(event);
const currentPath = (query.path as string) || '';
const bucket = (query.bucket as string) || 'client-portal'; // Default bucket
// Parse multipart form data
const form = formidable({
maxFileSize: 50 * 1024 * 1024, // 50MB limit
keepExtensions: true,
});
const [fields, files] = await form.parse(event.node.req);
// Handle multiple files
const uploadedFiles = Array.isArray(files.file) ? files.file : [files.file];
const results = [];
for (const uploadedFile of uploadedFiles) {
if (!uploadedFile) continue;
// Read file buffer
const fileBuffer = await fs.readFile(uploadedFile.filepath);
// Generate unique filename to prevent collisions
const timestamp = Date.now();
const sanitizedName = uploadedFile.originalFilename?.replace(/[^a-zA-Z0-9.-]/g, '_') || 'file';
const fileName = `${timestamp}-${sanitizedName}`;
// Construct full path including current folder
const fullPath = currentPath ? `${currentPath}${fileName}` : fileName;
// Get content type
const contentType = mime.lookup(uploadedFile.originalFilename || '') || 'application/octet-stream';
// Upload to MinIO - handle different buckets
if (bucket === 'client-portal') {
await uploadFile(fullPath, fileBuffer, contentType);
} else {
// For other buckets, use the MinIO client directly
const client = getMinioClient();
await client.putObject(bucket, fullPath, fileBuffer, fileBuffer.length, {
'Content-Type': contentType,
});
}
// Clean up temp file
await fs.unlink(uploadedFile.filepath);
results.push({
fileName: fullPath,
path: fullPath,
originalName: uploadedFile.originalFilename,
size: uploadedFile.size,
contentType,
bucket: bucket
});
// Log audit event
await logAuditEvent(event, 'upload', fullPath, uploadedFile.size);
}
// Return the first file's info for single file uploads (backward compatibility)
if (results.length === 1) {
return {
success: true,
path: results[0].path,
fileName: results[0].fileName,
files: results,
message: `File uploaded successfully`,
};
}
return {
success: true,
files: results,
message: `${results.length} file(s) uploaded successfully`,
};
} catch (error: any) {
console.error('Failed to upload file:', error);
throw createError({
statusCode: 500,
statusMessage: error.message || 'Failed to upload file',
});
}
});
// Audit logging helper
async function logAuditEvent(event: any, action: string, filePath: string, fileSize?: number) {
try {
const user = event.context.user || { email: 'anonymous' };
const auditLog = {
user_email: user.email,
action,
file_path: filePath,
file_size: fileSize,
timestamp: new Date().toISOString(),
ip_address: getClientIP(event),
success: true,
};
// You can store this in your database or logging system
console.log('Audit log:', auditLog);
} catch (error) {
console.error('Failed to log audit event:', error);
}
}
function getClientIP(event: any): string {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress ||
'unknown';
}