166 lines
4.9 KiB
JavaScript
166 lines
4.9 KiB
JavaScript
const crypto = require('crypto');
|
|
const fs = require('fs');
|
|
const multer = require('multer');
|
|
const path = require('path');
|
|
const {
|
|
getTripStatusUploadsDir,
|
|
getTripStatusFallbackUploadsDir,
|
|
replicateUploadedFilesToRemote,
|
|
removeUploadedTripStatusFiles
|
|
} = require('../services/tripStatusPhotoStorage');
|
|
const { appendPostLog } = require('../utils/postLog');
|
|
|
|
const MAX_TRIP_STATUS_PHOTO_SIZE_BYTES = 15 * 1024 * 1024;
|
|
const MAX_TRIP_STATUS_FILES = 5;
|
|
const ALLOWED_MIME_TYPES = new Set([
|
|
'image/jpeg',
|
|
'image/jpg',
|
|
'image/png',
|
|
'image/webp',
|
|
'image/heic',
|
|
'image/heif'
|
|
]);
|
|
|
|
const getTripDirectorySegment = (req) => {
|
|
const tripId = Number.parseInt(req.params?.id, 10);
|
|
return Number.isInteger(tripId) && tripId > 0 ? String(tripId) : 'unknown';
|
|
};
|
|
|
|
const getTripStatusUploadsTripDir = (req) =>
|
|
path.join(getTripStatusUploadsDir(), getTripDirectorySegment(req));
|
|
const getTripStatusFallbackUploadsTripDir = (req) =>
|
|
path.join(getTripStatusFallbackUploadsDir(), getTripDirectorySegment(req));
|
|
|
|
const ensureTripStatusUploadsDir = (req) => {
|
|
const primaryTripDir = getTripStatusUploadsTripDir(req);
|
|
|
|
try {
|
|
fs.mkdirSync(primaryTripDir, { recursive: true });
|
|
return primaryTripDir;
|
|
} catch (primaryError) {
|
|
const fallbackTripDir = getTripStatusFallbackUploadsTripDir(req);
|
|
|
|
fs.mkdirSync(fallbackTripDir, { recursive: true });
|
|
return fallbackTripDir;
|
|
}
|
|
};
|
|
|
|
const getExtensionFromMimeType = (mimeType) => {
|
|
if (mimeType === 'image/png') {
|
|
return '.png';
|
|
}
|
|
|
|
if (mimeType === 'image/webp') {
|
|
return '.webp';
|
|
}
|
|
|
|
if (mimeType === 'image/heic') {
|
|
return '.heic';
|
|
}
|
|
|
|
if (mimeType === 'image/heif') {
|
|
return '.heif';
|
|
}
|
|
|
|
return '.jpg';
|
|
};
|
|
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => {
|
|
try {
|
|
cb(null, ensureTripStatusUploadsDir(req));
|
|
} catch (error) {
|
|
cb(error);
|
|
}
|
|
},
|
|
filename: (req, file, cb) => {
|
|
const token = crypto.randomBytes(3).toString('hex');
|
|
const extension = getExtensionFromMimeType(file.mimetype);
|
|
cb(null, `${token}${extension}`);
|
|
}
|
|
});
|
|
|
|
const internalUpload = multer({
|
|
storage,
|
|
limits: {
|
|
fileSize: MAX_TRIP_STATUS_PHOTO_SIZE_BYTES,
|
|
files: MAX_TRIP_STATUS_FILES
|
|
},
|
|
fileFilter: (req, file, cb) => {
|
|
if (!ALLOWED_MIME_TYPES.has(file.mimetype)) {
|
|
return cb(new Error('INVALID_FILE_TYPE'));
|
|
}
|
|
|
|
cb(null, true);
|
|
}
|
|
});
|
|
|
|
const uploadTripStatusPhotos = (req, res, next) => {
|
|
internalUpload.fields([
|
|
{ name: 'fotos', maxCount: MAX_TRIP_STATUS_FILES },
|
|
{ name: 'fotos[]', maxCount: MAX_TRIP_STATUS_FILES }
|
|
])(req, res, async (error) => {
|
|
if (!error) {
|
|
const uploadedFiles = collectUploadedTripStatusFiles(req);
|
|
const authorizationHeader = req.get('authorization');
|
|
|
|
appendPostLog({
|
|
request_id: req.requestId || null,
|
|
method: req.method,
|
|
path: req.originalUrl || req.url,
|
|
ip: req.ip || null,
|
|
content_type: req.get('content-type') || null,
|
|
has_authorization_header: Boolean(authorizationHeader),
|
|
authorization: authorizationHeader ? '[REDACTED]' : null,
|
|
query: req.query || {},
|
|
body: req.body || {},
|
|
raw_body: null,
|
|
files: uploadedFiles.map((file) => ({
|
|
field_name: file.fieldname || null,
|
|
filename: file.filename || null,
|
|
originalname: file.originalname || null,
|
|
mimetype: file.mimetype || null,
|
|
size: Number.isFinite(file.size) ? file.size : null
|
|
}))
|
|
});
|
|
await replicateUploadedFilesToRemote({
|
|
tripId: req.params?.id,
|
|
files: uploadedFiles
|
|
});
|
|
return next();
|
|
}
|
|
|
|
if (error instanceof multer.MulterError) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid payload'
|
|
});
|
|
}
|
|
|
|
if (error.message === 'INVALID_FILE_TYPE') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid payload'
|
|
});
|
|
}
|
|
|
|
console.error('Error uploading trip status photos:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Internal server error'
|
|
});
|
|
});
|
|
};
|
|
|
|
const collectUploadedTripStatusFiles = (req) => [
|
|
...(Array.isArray(req.files?.fotos) ? req.files.fotos : []),
|
|
...(Array.isArray(req.files?.['fotos[]']) ? req.files['fotos[]'] : [])
|
|
];
|
|
|
|
module.exports = {
|
|
uploadTripStatusPhotos,
|
|
collectUploadedTripStatusFiles,
|
|
removeUploadedTripStatusFiles,
|
|
MAX_TRIP_STATUS_FILES
|
|
};
|