From b2626918d35d11932a44e2d8587f6b6cd2770706 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Nov 2025 20:05:44 +0000 Subject: [PATCH] Fix issue #30: Critical bugs in Reference (external folder) mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes the core bugs that prevented Reference mode from functioning: 1. Missing external_relpath Error (CRITICAL FIX) - Root cause: photoResolver prioritized event.source_mode over photo.source_origin - Problem: Events in "reference" mode with uploaded photos would fail because uploaded photos have source_origin='managed' but were being treated as external photos (requiring external_relpath) - Fix: Prioritize photo.source_origin over event.source_mode - Result: Events can now have MIXED sources - imported external photos AND newly uploaded managed photos coexisting correctly - File: backend/src/services/photoResolver.js:19 2. Category Assignment Failure (CRITICAL FIX) - Root cause: Update endpoints modified category_id column but display used photo.type field ('individual' or 'collage') - Problem: Category changes appeared to succeed but had no visible effect - Fix: When category_id is 'individual' or 'collage', update the type field instead of category_id - Result: Category assignments now work correctly for all photos - Files: backend/src/routes/adminPhotos.js:489-497, 605-607 3. Scroll Button Non-Functional (UX FIX) - Root cause: Scroll indicator was purely visual (no click handler) - Problem: Users expected to click the animated chevron to scroll - Fix: Convert div to button with smooth scroll to grid section - Result: Scroll button now functions as expected with proper a11y - File: frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx:165-184 Technical Details: Mixed Source Support: The photoResolver now correctly handles events that mix: - External photos: source_origin='external' + external_relpath set - Uploaded photos: source_origin='managed' + path in storage/events/active This allows users to start with external media import and later upload additional photos without errors. Category/Type Distinction: The system uses photo.type ('individual'|'collage') for display but also has a legacy category_id column. The update logic now handles both: - String values 'individual'/'collage' → update type field - Numeric values → update legacy category_id field (backward compat) Notes on Remaining Issues: Issue #30 also mentioned: 4. Image display (cropped square) - This is by design. Thumbnails use fit='cover' by default for consistent grid layouts. Can be changed via app_settings.thumbnail_fit if needed. 5. Theme application - The "Apply Theme" button updates the form state correctly. Users need to click "Save Changes" to persist to database. This is standard form behavior, not a bug. Testing: - Create event in reference mode with external media - Upload new photos to the same event → verify no external_relpath error - Change categories on both external and uploaded photos → verify changes apply - Use Hero gallery layout → verify scroll button works Fixes #30 --- backend/src/routes/adminPhotos.js | 39 ++++++++++++++----- backend/src/services/photoResolver.js | 8 +++- .../gallery/layouts/HeroGalleryLayout.tsx | 19 +++++++-- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/backend/src/routes/adminPhotos.js b/backend/src/routes/adminPhotos.js index 57fe885..bb2834e 100644 --- a/backend/src/routes/adminPhotos.js +++ b/backend/src/routes/adminPhotos.js @@ -473,21 +473,34 @@ router.patch('/:eventId/photos/:photoId', adminAuth, async (req, res) => { try { const { eventId, photoId } = req.params; const { category_id } = req.body; - + // Verify photo belongs to event const photo = await db('photos') .where({ id: photoId, event_id: eventId }) .first(); - + if (!photo) { return res.status(404).json({ error: 'Photo not found' }); } - + + // Prepare update data + const updateData = {}; + + // Handle type-based categories ('individual' or 'collage') + // These are string values that map to the photo.type field + if (category_id === 'individual' || category_id === 'collage') { + updateData.type = category_id; + updateData.category_id = null; // Clear legacy category_id + } else { + // Handle legacy numeric category IDs + updateData.category_id = category_id || null; + } + // Update photo await db('photos') .where({ id: photoId }) - .update({ category_id: category_id || null }); - + .update(updateData); + res.json({ message: 'Photo updated successfully' }); } catch (error) { console.error('Error updating photo:', error); @@ -584,17 +597,25 @@ router.post('/:eventId/photos/bulk-update', adminAuth, async (req, res) => { return res.status(400).json({ error: 'Some photos do not belong to this event' }); } - // Update photos + // Prepare update data const updateData = {}; if (updates.category_id !== undefined) { - updateData.category_id = updates.category_id || null; + // Handle type-based categories ('individual' or 'collage') + // These are string values that map to the photo.type field + if (updates.category_id === 'individual' || updates.category_id === 'collage') { + updateData.type = updates.category_id; + updateData.category_id = null; // Clear legacy category_id + } else { + // Handle legacy numeric category IDs + updateData.category_id = updates.category_id || null; + } } - + await db('photos') .whereIn('id', photoIds) .where('event_id', eventId) .update(updateData); - + res.json({ message: `${photoIds.length} photos updated successfully` }); } catch (error) { console.error('Error bulk updating photos:', error); diff --git a/backend/src/services/photoResolver.js b/backend/src/services/photoResolver.js index 18e3f3c..340fd9e 100644 --- a/backend/src/services/photoResolver.js +++ b/backend/src/services/photoResolver.js @@ -12,8 +12,12 @@ const getStoragePath = () => process.env.STORAGE_PATH || path.join(__dirname, '. function resolvePhotoFilePath(event, photo) { if (!event || !photo) throw new Error('resolvePhotoFilePath requires event and photo'); - const mode = (event.source_mode || photo.source_origin || 'managed'); - if (mode === 'reference' || photo.source_origin === 'external') { + // IMPORTANT: photo.source_origin takes precedence over event.source_mode + // This allows events in "reference" mode to have mixed sources: + // - Imported photos: source_origin = 'external' + // - Uploaded photos: source_origin = 'managed' + const mode = (photo.source_origin || event.source_mode || 'managed'); + if (mode === 'reference' || mode === 'external') { if (!photo.external_relpath) { throw new Error('Missing external_relpath for external photo'); } diff --git a/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx b/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx index 630fcf9..660951c 100644 --- a/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx +++ b/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx @@ -162,13 +162,26 @@ export const HeroGalleryLayout: React.FC = ({ {/* Scroll Indicator */} -
+
+ {/* Grid Section */} -
+