From 8c87f1537bcb5640025dff288ff47a4c1995fd30 Mon Sep 17 00:00:00 2001 From: paul Date: Fri, 28 Nov 2025 16:33:32 +0100 Subject: [PATCH] Resolve merge conflicts for video uploads and processing --- backend/src/routes/adminPhotos.js | 15 ++++++-- backend/src/services/photoProcessor.js | 53 ++++++++++++++------------ 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/backend/src/routes/adminPhotos.js b/backend/src/routes/adminPhotos.js index d767955..2feaf02 100644 --- a/backend/src/routes/adminPhotos.js +++ b/backend/src/routes/adminPhotos.js @@ -76,12 +76,15 @@ const upload = multer({ }, fileFilter: (req, file, cb) => { // Accept images and common video formats with proper validation - const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp', 'video/mp4', 'video/quicktime', 'video/webm']; + const allowedMimeTypes = [ + 'image/jpeg', 'image/png', 'image/webp', + 'video/mp4', 'video/webm', 'video/quicktime', 'video/x-msvideo' + ]; if (validateFileType(file.originalname, file.mimetype, allowedMimeTypes)) { return cb(null, true); } else { - cb(new Error('Only JPEG, PNG, WebP images or MP4/MOV/WEBM videos are allowed')); + cb(new Error('Only JPEG, PNG, WebP images and MP4, WebM, MOV, AVI videos are allowed')); } }, // Add abort on limit to stop processing when limits are exceeded @@ -90,8 +93,12 @@ const upload = multer({ // Create content validator middleware const validateUploadContent = createFileUploadValidator({ - allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'video/mp4', 'video/quicktime', 'video/webm'], - maxFileSize: 50 * 1024 * 1024, + allowedTypes: [ + 'image/jpeg', 'image/png', 'image/webp', + 'video/mp4', 'video/webm', 'video/quicktime', 'video/x-msvideo' + ], + // 10GB per file to accommodate large videos; overall limits enforced elsewhere + maxFileSize: 10 * 1024 * 1024 * 1024, validateContent: true }); diff --git a/backend/src/services/photoProcessor.js b/backend/src/services/photoProcessor.js index 99d5832..75274a7 100644 --- a/backend/src/services/photoProcessor.js +++ b/backend/src/services/photoProcessor.js @@ -2,6 +2,7 @@ const path = require('path'); const fs = require('fs').promises; const { db } = require('../database/db'); const { generateThumbnail, generateVideoPlaceholder } = require('./imageProcessor'); +const { processUploadedVideo } = require('./videoProcessor'); const { generatePhotoFilename } = require('../utils/filenameSanitizer'); const { isVideoMimeType } = require('../utils/fileSecurityUtils'); const mime = require('mime-types'); @@ -155,10 +156,21 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ } } - // Generate thumbnail or placeholder + // Generate thumbnail and metadata let thumbnailPath = null; + let videoMetadata = null; if (isVideo) { - thumbnailPath = await generateVideoPlaceholder(newFilename); + const thumbnailDir = path.join(getStoragePath(), 'thumbnails'); + await fs.mkdir(thumbnailDir, { recursive: true }); + const videoThumbnailPath = path.join(thumbnailDir, `thumb_${newFilename.replace(/\.[^.]+$/, '.jpg')}`); + try { + const result = await processUploadedVideo(newPath, videoThumbnailPath); + videoMetadata = result?.metadata || null; + thumbnailPath = path.relative(getStoragePath(), videoThumbnailPath); + } catch (videoErr) { + console.error('Failed to process uploaded video, falling back to placeholder:', videoErr.message); + thumbnailPath = await generateVideoPlaceholder(newFilename); + } } else { thumbnailPath = await generateThumbnail(newPath); } @@ -173,32 +185,25 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ const clientName = trx?.client?.config?.client; const supportsReturning = ['pg', 'postgres', 'postgresql'].includes(clientName); + const photoData = { + event_id: eventId, + filename: newFilename, + path: relativePath, + thumbnail_path: relativeThumbPath, + type: photoType, + size_bytes: file.size, + uploaded_by: uploadedBy, + source_origin: 'managed', + mime_type: resolvedMime, + media_type: isVideo ? 'video' : 'photo' + }; + if (supportsReturning) { insertResult = await trx('photos') - .insert({ - event_id: eventId, - filename: newFilename, - path: relativePath, - thumbnail_path: relativeThumbPath, - type: photoType, - size_bytes: file.size, - uploaded_by: uploadedBy, - source_origin: 'managed', - mime_type: resolvedMime - }) + .insert(photoData) .returning('id'); } else { - insertResult = await trx('photos').insert({ - event_id: eventId, - filename: newFilename, - path: relativePath, - thumbnail_path: relativeThumbPath, - type: photoType, - size_bytes: file.size, - uploaded_by: uploadedBy, - source_origin: 'managed', - mime_type: resolvedMime - }); + insertResult = await trx('photos').insert(photoData); } const insertedId = Array.isArray(insertResult)