fix(feedback): persist guest feedback settings, unshadow the guest route (#1030) (#1031)

Enabling Guest Feedback on an event could silently do nothing.

1. `updateEventFeedbackSettings` spread the request body straight into the
   knex UPDATE. The admin event form posts its whole client-side state,
   including three keys that were never columns on event_feedback_settings
   (`enable_rate_limiting`, `rate_limit_window_minutes`,
   `rate_limit_max_requests`), so the write threw and the route answered 500.
   Writable columns are now whitelisted; identity columns and timestamps stay
   server-managed.

2. EventDetailsPage swallowed that 500 in a bare `catch {}` ("Error already
   handled by mutation" — it is a different request), so the admin was left
   looking at "Event updated successfully" while the toggle never persisted.
   The error is surfaced now and the settings query is invalidated on success.

3. gallery.js declared a duplicate `GET /:slug/feedback-settings`. server.js
   mounts galleryRoutes before galleryFeedback, so it shadowed the real
   handler and dropped the per-guest caps (#655) from the guest payload — the
   gallery could never render the favorite/like limits or their counters.

Timestamps are written as ISO strings so they round-trip on both engines.

Claude-Session: https://claude.ai/code/session_0168gubtwYYacJv8weAjy8DM

Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
Paul Nothaft
2026-08-13 18:50:52 +02:00
committed by GitHub
co-authored by Paul Nothaft
parent 34ee31141b
commit 89dc9623c1
4 changed files with 220 additions and 33 deletions
+44 -9
View File
@@ -3,6 +3,39 @@ const logger = require('../utils/logger');
const { formatBoolean } = require('../utils/dbCompat');
const { REACTION_EMOJIS } = require('../constants/reactions');
// Every writable column on event_feedback_settings (#1030). The admin form
// posts its whole client-side state back, including UI-only keys that were
// never columns — `enable_rate_limiting`, `rate_limit_window_minutes`,
// `rate_limit_max_requests` — and spreading those into the UPDATE made knex
// throw, so the request 500'd and the "Enable feedback" toggle silently
// never persisted. Identity columns (id/event_id) and the timestamps stay
// server-managed. New columns MUST be added here.
const FEEDBACK_SETTINGS_COLUMNS = [
'feedback_enabled',
'allow_ratings',
'allow_likes',
'allow_comments',
'allow_favorites',
'allow_reactions',
'require_name_email',
'moderate_comments',
'require_moderation',
'show_feedback_to_guests',
'identity_mode',
'max_favorites_per_guest',
'max_likes_per_guest'
];
function pickSettingsColumns(settings) {
const picked = {};
for (const column of FEEDBACK_SETTINGS_COLUMNS) {
if (Object.prototype.hasOwnProperty.call(settings || {}, column)) {
picked[column] = settings[column];
}
}
return picked;
}
class FeedbackService {
/**
* Get feedback settings for an event
@@ -55,25 +88,27 @@ class FeedbackService {
const existing = await db('event_feedback_settings')
.where('event_id', eventId)
.first();
const writable = pickSettingsColumns(settings);
if (existing) {
await db('event_feedback_settings')
.where('event_id', eventId)
.update({
...settings,
updated_at: new Date()
...writable,
updated_at: new Date().toISOString()
});
} else {
await db('event_feedback_settings').insert({
event_id: eventId,
...settings,
created_at: new Date(),
updated_at: new Date()
...writable,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
});
}
await logActivity('feedback_settings_updated', settings, eventId);
await logActivity('feedback_settings_updated', writable, eventId);
return this.getEventFeedbackSettings(eventId);
} catch (error) {
logger.error('Error updating feedback settings:', error);