Files
hope-events/backend/src/utils/accessLog.js
T
joshuaandClaude Opus 5.5 39885a8719 Volunteer rosters: roles, sign-ups, swaps, QR check-in, kids-room duty (backend)
Serving roles per event or session, sign-ups and assigning with an overlap
check, swaps that happen on accept (notify recipients emailed), QR and manual
check-in (kids-room roles start rostered duty), optional free volunteer ticket,
day-before reminder job, hours served, privacy hooks. The kids check-in and
volunteer tickets are left out of public event pages and public registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 03:59:52 +02:00

63 lines
2.6 KiB
JavaScript

const prisma = require('../config/db');
const { getClientIp } = require('./requestUtils');
// What each DataAccessLog action means, for the admin page.
const ACCESS_ACTIONS = {
report_pdf: 'Report downloaded (PDF)',
report_excel: 'Report downloaded (Excel)',
report_email: 'Report emailed',
report_whatsapp: 'Report sent by WhatsApp',
attendance_export: 'Attendance register exported',
certificates_download: 'Certificates downloaded',
group_lists: 'Room/group lists opened',
form_responses: 'Attendee form answers viewed',
consent_list: 'Parental consent list viewed',
medical_list: 'Medical list viewed',
backup_download: 'Database backup downloaded',
impersonation: 'Viewed the site as another user',
kids_rooms: 'Kids check-in desk or room opened',
kids_offline: 'Kids check-in list downloaded for offline use',
volunteer_roster: 'Volunteer roster opened',
};
/**
* POPIA access log: someone exported or viewed personal information in bulk. Fire-and-
* forget like logAdminAction — a logging failure never blocks the export itself.
*/
async function logDataAccess(req, { action, what, eventId = null, records = null, sensitive }) {
try {
// Children's or special information: medical lists always; otherwise anything about a
// youth event (one that asks for parental consent).
let isSensitive = sensitive ?? (action === 'medical_list');
if (!isSensitive && eventId) {
const event = await prisma.event.findUnique({ where: { id: eventId }, select: { requiresParentalConsent: true } });
isSensitive = !!event?.requiresParentalConsent;
}
await prisma.dataAccessLog.create({
data: {
actorId: req.user?.id || null,
actorRole: req.user?.role || 'unknown',
action,
what: String(what || ACCESS_ACTIONS[action] || action).slice(0, 500),
eventId: eventId || null,
records: Number.isFinite(records) ? records : null,
sensitive: isSensitive,
ip: getClientIp(req),
},
});
} catch (e) {
console.error('Failed to log data access:', e?.message);
}
}
/** Rows in a report payload (main table or layered sections, plus extra tables). */
function reportRowCount(payload) {
const p = payload || {};
let n = Array.isArray(p.table?.rows) ? p.table.rows.length : 0;
if (Array.isArray(p.layered?.sections)) n += p.layered.sections.reduce((s, sec) => s + (Array.isArray(sec.items) ? sec.items.length : 0), 0);
if (Array.isArray(p.extraTables)) n += p.extraTables.reduce((s, t) => s + (Array.isArray(t.rows) ? t.rows.length : 0), 0);
return n;
}
module.exports = { ACCESS_ACTIONS, logDataAccess, reportRowCount };