115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
import { Response, Router } from 'express';
|
|
import type { Router as RouterType } from 'express';
|
|
import { randomUUID } from 'crypto';
|
|
import { StorageFactory } from '../services/StorageFactory';
|
|
import { asyncHandler } from '../middleware/errorHandler';
|
|
import { validateApiKey } from '../middleware/auth/validateApiKey';
|
|
import { requireProjectKey } from '../middleware/auth/requireProjectKey';
|
|
import { rateLimitByApiKey } from '../middleware/auth/rateLimiter';
|
|
import { uploadSingleImage, handleUploadErrors } from '../middleware/upload';
|
|
import { UploadFileResponse } from '../types/api';
|
|
|
|
export const uploadRouter: RouterType = Router();
|
|
|
|
/**
|
|
* POST /api/upload - Upload a single image file
|
|
*/
|
|
uploadRouter.post(
|
|
'/upload',
|
|
// Authentication middleware
|
|
validateApiKey,
|
|
requireProjectKey,
|
|
rateLimitByApiKey,
|
|
|
|
// File upload middleware
|
|
uploadSingleImage,
|
|
handleUploadErrors,
|
|
|
|
// Main handler
|
|
asyncHandler(async (req: any, res: Response) => {
|
|
const timestamp = new Date().toISOString();
|
|
const requestId = req.requestId;
|
|
|
|
// Check if file was provided
|
|
if (!req.file) {
|
|
const errorResponse: UploadFileResponse = {
|
|
success: false,
|
|
message: 'File upload failed',
|
|
error: 'No file provided',
|
|
};
|
|
return res.status(400).json(errorResponse);
|
|
}
|
|
|
|
// Extract org/project slugs from validated API key
|
|
const orgSlug = req.apiKey?.organizationSlug || process.env['DEFAULT_ORG_SLUG'] || 'default';
|
|
const projectSlug = req.apiKey?.projectSlug || process.env['DEFAULT_PROJECT_SLUG'] || 'main'; // Guaranteed by requireProjectKey middleware
|
|
|
|
console.log(
|
|
`[${timestamp}] [${requestId}] Starting file upload for org:${orgSlug}, project:${projectSlug}`,
|
|
);
|
|
|
|
const file = req.file;
|
|
|
|
try {
|
|
// Initialize storage service
|
|
const storageService = await StorageFactory.getInstance();
|
|
|
|
// Generate imageId (UUID) - this will be the filename in storage
|
|
const imageId = randomUUID();
|
|
|
|
// Upload file to MinIO
|
|
// Path format: {orgSlug}/{projectSlug}/img/{imageId}
|
|
console.log(
|
|
`[${timestamp}] [${requestId}] Uploading file: ${file.originalname} as ${imageId} (${file.size} bytes)`,
|
|
);
|
|
|
|
const uploadResult = await storageService.uploadFile(
|
|
orgSlug,
|
|
projectSlug,
|
|
imageId,
|
|
file.buffer,
|
|
file.mimetype,
|
|
file.originalname,
|
|
);
|
|
|
|
if (!uploadResult.success) {
|
|
const errorResponse: UploadFileResponse = {
|
|
success: false,
|
|
message: 'File upload failed',
|
|
error: uploadResult.error || 'Storage service error',
|
|
};
|
|
return res.status(500).json(errorResponse);
|
|
}
|
|
|
|
// Prepare success response
|
|
const successResponse: UploadFileResponse = {
|
|
success: true,
|
|
message: 'File uploaded successfully',
|
|
data: {
|
|
filename: uploadResult.filename,
|
|
originalName: file.originalname,
|
|
path: uploadResult.path,
|
|
url: uploadResult.url,
|
|
size: uploadResult.size,
|
|
contentType: uploadResult.contentType,
|
|
uploadedAt: timestamp,
|
|
},
|
|
};
|
|
|
|
console.log(`[${timestamp}] [${requestId}] File uploaded successfully: ${uploadResult.url}`);
|
|
|
|
return res.status(200).json(successResponse);
|
|
} catch (error) {
|
|
console.error(`[${timestamp}] [${requestId}] Unhandled error in upload endpoint:`, error);
|
|
|
|
const errorResponse: UploadFileResponse = {
|
|
success: false,
|
|
message: 'File upload failed',
|
|
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
};
|
|
|
|
return res.status(500).json(errorResponse);
|
|
}
|
|
}),
|
|
);
|