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:
@@ -0,0 +1,52 @@
|
||||
const prisma = require('../config/db');
|
||||
|
||||
// Fire-and-forget by design — a logging failure must never break the underlying admin
|
||||
// action, so this swallows its own errors rather than propagating them to the caller
|
||||
// (same posture as logSecurityEvent).
|
||||
async function logAdminAction({ actorId, actorRole, action, targetType, targetId, metadata, ip }) {
|
||||
try {
|
||||
await prisma.adminAuditLog.create({
|
||||
data: {
|
||||
actorId: actorId || null,
|
||||
actorRole,
|
||||
action,
|
||||
targetType,
|
||||
targetId: targetId || null,
|
||||
metadata: metadata || undefined,
|
||||
ip: ip || null,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to log admin action:', e?.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Paginated listing for the admin audit-log page, with optional actor/action/date filters.
|
||||
async function getAdminAuditLog({ page = 1, limit = 50, actorId, action, from, to } = {}) {
|
||||
const where = {};
|
||||
if (actorId) where.actorId = actorId;
|
||||
if (action) where.action = action;
|
||||
if (from || to) {
|
||||
where.createdAt = {};
|
||||
if (from) where.createdAt.gte = new Date(from);
|
||||
if (to) where.createdAt.lte = new Date(to);
|
||||
}
|
||||
|
||||
const take = Math.min(Math.max(Number(limit) || 50, 1), 200);
|
||||
const skip = (Math.max(Number(page) || 1, 1) - 1) * take;
|
||||
|
||||
const [rows, total] = await Promise.all([
|
||||
prisma.adminAuditLog.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take,
|
||||
skip,
|
||||
include: { actor: { select: { id: true, name: true, email: true } } },
|
||||
}),
|
||||
prisma.adminAuditLog.count({ where }),
|
||||
]);
|
||||
|
||||
return { rows, total, page: Math.max(Number(page) || 1, 1), limit: take };
|
||||
}
|
||||
|
||||
module.exports = { logAdminAction, getAdminAuditLog };
|
||||
Reference in New Issue
Block a user