Add calendar export, SEO, error monitoring, backups, audit trail, and a starter test suite

Six site improvements picked from a "what could be better" review, plus a Jest
test suite covering the two areas with the trickiest money-handling history
in this project (early-bird pricing tranches, donation-leg accounting):

- "Add to calendar" .ics download on event pages and in confirmation emails
- sitemap.xml, robots.txt, and Open Graph/Twitter metadata for public pages
- Sentry error monitoring (backend + frontend), a no-op until SENTRY_DSN is set
- Nightly local pg_dump backups with a Site Settings tab to browse/trigger/download
- Admin audit trail for refunds, donations, manual registrations, event and
  settings changes, and staff-initiated cancellations
- Jest tests reproducing and guarding against the 1.8.0 tranche-pricing bug
  and the 1.4.2 donation-balance-inflation bug

Wallet passes (Google/Apple) were scoped out of this round — Apple Wallet
needs a paid Apple Developer account the project doesn't have yet, and the
user preferred shipping both together later rather than Google alone now.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 14:50:11 +02:00
co-authored by Claude Sonnet 5
parent 98ac26bf70
commit 54b89d4f4b
43 changed files with 8414 additions and 92 deletions
@@ -4,6 +4,8 @@ const { safeErrorMessage } = require('../utils/errorUtils');
const { v4: uuidv4 } = require('uuid');
const { invalidate: invalidateSettingsCache, warmCache, ENCRYPTED_KEYS } = require('../utils/settingsCache');
const { encrypt, decrypt, isEncrypted } = require('../utils/encryption');
const { logAdminAction } = require('../utils/adminAudit');
const { getClientIp } = require('../utils/requestUtils');
// Keys safe to return without auth — includes legal keys needed by public legal pages
const PUBLIC_KEYS = [
@@ -107,6 +109,20 @@ const updateSettings = async (req, res) => {
if (ops.length) await prisma.$transaction(ops);
invalidateSettingsCache();
await warmCache(); // ensure in-memory cache reflects the new values before responding
// Log which keys changed, never the values — some settings are secrets (e.g. the
// WAWP token) that aren't even encrypted at rest, let alone fit for an audit log.
if (ops.length) {
logAdminAction({
actorId: req.user.id,
actorRole: req.user.role,
action: 'settings_updated',
targetType: 'AppSetting',
metadata: { changedKeys: Object.keys(updates).filter(k => updates[k] !== undefined && updates[k] !== null) },
ip: getClientIp(req),
});
}
res.json({ message: 'Settings saved' });
} catch (e) {
res.status(res.statusCode === 200 ? 400 : res.statusCode).json({ message: safeErrorMessage(e) });