fix: stabilize uploads and guest feedback filters
Mirror to GitHub / mirror (push) Successful in 1m54s
Test and Lint / backend-test (push) Successful in 1m51s
Test and Lint / frontend-test (push) Successful in 2m11s
Version and Release / version-bump (push) Successful in 1m49s
Version and Release / trigger-drone (push) Successful in 3s

This commit is contained in:
2025-09-19 16:35:55 +02:00
parent 2a4d38813f
commit aaaf59817b
2 changed files with 109 additions and 44 deletions
+72 -34
View File
@@ -107,38 +107,82 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => {
.select('photos.*')
.orderBy('photos.uploaded_at', 'desc');
// Apply filtering if requested (global, based on aggregate counts)
// Apply filtering if requested (supports global stats + per-guest interactions)
if (filter) {
const f = String(filter).toLowerCase();
const parts = f.split(',').map(s => s.trim());
const include = new Set();
const filterTokens = new Set(
String(filter)
.toLowerCase()
.split(',')
.map(token => token.trim())
.filter(Boolean)
);
// Helper to include IDs for a predicate
const includeBy = (predicate) => {
photos.forEach(p => { if (predicate(p)) include.add(p.id); });
};
if (filterTokens.size > 0) {
// Treat "saved" / "favorite" synonyms as favorites
if (filterTokens.has('saved')) {
filterTokens.add('favorited');
}
if (filterTokens.has('favorite')) {
filterTokens.add('favorited');
}
if (parts.includes('liked')) {
includeBy(p => (p.like_count || 0) > 0);
}
if (parts.includes('favorited')) {
includeBy(p => (p.favorite_count || 0) > 0);
}
if (parts.includes('rated')) {
includeBy(p => (p.average_rating || 0) > 0);
}
if (parts.includes('commented')) {
// Query commented photo IDs
const commented = await db('photo_feedback')
.where({ event_id: req.event.id, feedback_type: 'comment', is_approved: true, is_hidden: false })
.groupBy('photo_id')
.select('photo_id');
const commentedIds = new Set(commented.map(c => c.photo_id));
includeBy(p => commentedIds.has(p.id));
}
const include = new Set();
if (include.size > 0) {
photos = photos.filter(p => include.has(p.id));
const includeBy = (predicate) => {
photos.forEach(photo => {
if (predicate(photo)) {
include.add(photo.id);
}
});
};
let guestFeedbackByType = null;
if (guest_id) {
const guestFeedbackRows = await db('photo_feedback')
.where({ event_id: req.event.id, guest_identifier: guest_id })
.select('photo_id', 'feedback_type');
guestFeedbackByType = guestFeedbackRows.reduce((acc, row) => {
if (!acc[row.feedback_type]) {
acc[row.feedback_type] = new Set();
}
acc[row.feedback_type].add(row.photo_id);
return acc;
}, {});
}
const includeGuestMatches = (type) => {
const ids = guestFeedbackByType?.[type];
if (ids && ids.size > 0) {
ids.forEach(id => include.add(id));
}
};
if (filterTokens.has('liked')) {
includeGuestMatches('like');
includeBy(photo => (photo.like_count || 0) > 0);
}
if (filterTokens.has('favorited')) {
includeGuestMatches('favorite');
includeBy(photo => (photo.favorite_count || 0) > 0);
}
if (filterTokens.has('rated')) {
includeGuestMatches('rating');
includeBy(photo => (photo.average_rating || 0) > 0);
}
if (filterTokens.has('commented')) {
includeGuestMatches('comment');
const commentedRows = await db('photo_feedback')
.where({ event_id: req.event.id, feedback_type: 'comment', is_approved: true, is_hidden: false })
.groupBy('photo_id')
.select('photo_id');
commentedRows.forEach(row => include.add(row.photo_id));
}
photos = photos.filter(photo => include.has(photo.id));
}
}
@@ -713,12 +757,6 @@ router.post('/:eventId/upload', verifyGalleryAccess, async (req, res) => {
// Process uploaded photos
const results = await processUploadedPhotos(req.files, eventId, 'user', categoryId);
// Clean up temp files
const fs = require('fs').promises;
for (const file of req.files) {
await fs.unlink(file.path).catch(console.error);
}
res.json({
message: 'Photos uploaded successfully',
count: results.length,
+37 -10
View File
@@ -66,15 +66,42 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
const relativeThumbPath = thumbnailPath; // thumbnailPath is already relative to storage root
// Add to database with uploaded_by field
const [photoId] = await trx('photos').insert({
event_id: eventId,
filename: newFilename,
path: relativePath,
thumbnail_path: relativeThumbPath,
type: photoType,
size_bytes: file.size
});
let insertResult;
const clientName = trx?.client?.config?.client;
const supportsReturning = ['pg', 'postgres', 'postgresql'].includes(clientName);
if (supportsReturning) {
insertResult = await trx('photos')
.insert({
event_id: eventId,
filename: newFilename,
path: relativePath,
thumbnail_path: relativeThumbPath,
type: photoType,
size_bytes: file.size
})
.returning('id');
} else {
insertResult = await trx('photos').insert({
event_id: eventId,
filename: newFilename,
path: relativePath,
thumbnail_path: relativeThumbPath,
type: photoType,
size_bytes: file.size
});
}
const insertedId = Array.isArray(insertResult)
? (insertResult[0]?.id ?? insertResult[0])
: insertResult;
const photoId = typeof insertedId === 'object' ? insertedId.id : insertedId;
if (photoId === undefined || photoId === null) {
throw new Error('Failed to determine inserted photo ID');
}
// Commit transaction
await trx.commit();
@@ -96,4 +123,4 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
module.exports = {
processUploadedPhotos
};
};