Files
hope-events/backend/src/utils/adminAudit.js
T
joshuaandClaude Sonnet 5 54b89d4f4b 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>
2026-08-27 14:50:11 +02:00

53 lines
1.7 KiB
JavaScript

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 };