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
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:
@@ -107,38 +107,82 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => {
|
|||||||
.select('photos.*')
|
.select('photos.*')
|
||||||
.orderBy('photos.uploaded_at', 'desc');
|
.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) {
|
if (filter) {
|
||||||
const f = String(filter).toLowerCase();
|
const filterTokens = new Set(
|
||||||
const parts = f.split(',').map(s => s.trim());
|
String(filter)
|
||||||
|
.toLowerCase()
|
||||||
|
.split(',')
|
||||||
|
.map(token => token.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filterTokens.size > 0) {
|
||||||
|
// Treat "saved" / "favorite" synonyms as favorites
|
||||||
|
if (filterTokens.has('saved')) {
|
||||||
|
filterTokens.add('favorited');
|
||||||
|
}
|
||||||
|
if (filterTokens.has('favorite')) {
|
||||||
|
filterTokens.add('favorited');
|
||||||
|
}
|
||||||
|
|
||||||
const include = new Set();
|
const include = new Set();
|
||||||
|
|
||||||
// Helper to include IDs for a predicate
|
|
||||||
const includeBy = (predicate) => {
|
const includeBy = (predicate) => {
|
||||||
photos.forEach(p => { if (predicate(p)) include.add(p.id); });
|
photos.forEach(photo => {
|
||||||
|
if (predicate(photo)) {
|
||||||
|
include.add(photo.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (parts.includes('liked')) {
|
let guestFeedbackByType = null;
|
||||||
includeBy(p => (p.like_count || 0) > 0);
|
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();
|
||||||
}
|
}
|
||||||
if (parts.includes('favorited')) {
|
acc[row.feedback_type].add(row.photo_id);
|
||||||
includeBy(p => (p.favorite_count || 0) > 0);
|
return acc;
|
||||||
|
}, {});
|
||||||
}
|
}
|
||||||
if (parts.includes('rated')) {
|
|
||||||
includeBy(p => (p.average_rating || 0) > 0);
|
const includeGuestMatches = (type) => {
|
||||||
|
const ids = guestFeedbackByType?.[type];
|
||||||
|
if (ids && ids.size > 0) {
|
||||||
|
ids.forEach(id => include.add(id));
|
||||||
}
|
}
|
||||||
if (parts.includes('commented')) {
|
};
|
||||||
// Query commented photo IDs
|
|
||||||
const commented = await db('photo_feedback')
|
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 })
|
.where({ event_id: req.event.id, feedback_type: 'comment', is_approved: true, is_hidden: false })
|
||||||
.groupBy('photo_id')
|
.groupBy('photo_id')
|
||||||
.select('photo_id');
|
.select('photo_id');
|
||||||
const commentedIds = new Set(commented.map(c => c.photo_id));
|
commentedRows.forEach(row => include.add(row.photo_id));
|
||||||
includeBy(p => commentedIds.has(p.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (include.size > 0) {
|
photos = photos.filter(photo => include.has(photo.id));
|
||||||
photos = photos.filter(p => include.has(p.id));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,12 +757,6 @@ router.post('/:eventId/upload', verifyGalleryAccess, async (req, res) => {
|
|||||||
// Process uploaded photos
|
// Process uploaded photos
|
||||||
const results = await processUploadedPhotos(req.files, eventId, 'user', categoryId);
|
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({
|
res.json({
|
||||||
message: 'Photos uploaded successfully',
|
message: 'Photos uploaded successfully',
|
||||||
count: results.length,
|
count: results.length,
|
||||||
|
|||||||
@@ -66,7 +66,23 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
|
|||||||
const relativeThumbPath = thumbnailPath; // thumbnailPath is already relative to storage root
|
const relativeThumbPath = thumbnailPath; // thumbnailPath is already relative to storage root
|
||||||
|
|
||||||
// Add to database with uploaded_by field
|
// Add to database with uploaded_by field
|
||||||
const [photoId] = await trx('photos').insert({
|
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,
|
event_id: eventId,
|
||||||
filename: newFilename,
|
filename: newFilename,
|
||||||
path: relativePath,
|
path: relativePath,
|
||||||
@@ -74,6 +90,17 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
|
|||||||
type: photoType,
|
type: photoType,
|
||||||
size_bytes: file.size
|
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
|
// Commit transaction
|
||||||
await trx.commit();
|
await trx.commit();
|
||||||
|
|||||||
Reference in New Issue
Block a user