Files
hope-events/backend/src/controllers/analyticsController.js
T
joshuaandClaude Opus 5.5 344f260011 Campus-scoped staff: schema, campusScope helper, route test, first 79 routes
UserCampusScope relation and audit action; scopeEvent/scopeList/unscopedOnly/
campusIndependent/scopeBroadcast markers; tests/campusScopeRoutes.test.js
(still failing: events, payments, registrations, scan locks, tickets left).
Can't delete a campus that has scoped staff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:28:30 +02:00

49 lines
2.4 KiB
JavaScript

const prisma = require('../config/db');
const { safeErrorMessage } = require('../utils/errorUtils');
const { track, funnelFor } = require('../utils/analytics');
// @desc Record a funnel step (event page view, registration started)
// @route POST /api/analytics/track { sessionId, eventId, stage: 'view'|'start', source? }
// @access Public — only sent by browsers whose visitor accepted cookies
const trackStage = async (req, res) => {
try {
const { sessionId, eventId, stage, source } = req.body || {};
await track({ sessionId, eventId, stage, source, userAgent: req.headers['user-agent'] });
} catch (e) {
console.warn('[analytics] track failed:', e?.message || e);
}
// Always a quiet 204 — tracking must never get in a visitor's way.
res.status(204).end();
};
// @desc Registration funnel for one or more events
// @route GET /api/analytics/funnel?eventIds=a,b&from=YYYY-MM-DD&to=YYYY-MM-DD
// @access Private/Supervisor
const getFunnel = async (req, res) => {
try {
const asked = String(req.query.eventIds || '').split(',').map(s => s.trim()).filter(Boolean).slice(0, 50);
if (asked.length === 0) { res.status(400); throw new Error('Choose at least one event'); }
// Campus-scoped supervisors only get the events they can see.
const scopeWhere = await require('../utils/campusScope').listWhere(req);
const events = await prisma.event.findMany({ where: { id: { in: asked }, ...scopeWhere }, select: { id: true, title: true } });
const eventIds = events.map(e => e.id);
if (eventIds.length === 0) { res.status(404); throw new Error('Event not found'); }
const total = await funnelFor(eventIds, { from: req.query.from, to: req.query.to });
// Per event too, so events can be compared.
const perEvent = [];
if (events.length > 1) {
for (const ev of events) {
const f = await funnelFor([ev.id], { from: req.query.from, to: req.query.to });
perEvent.push({ eventId: ev.id, title: ev.title, totals: f.totals, conversion: f.conversion });
}
} else if (events[0]) {
perEvent.push({ eventId: events[0].id, title: events[0].title, totals: total.totals, conversion: total.conversion });
}
res.json({ ...total, perEvent });
} catch (error) {
res.status(res.statusCode === 200 ? 400 : res.statusCode).json({ message: safeErrorMessage(error) });
}
};
module.exports = { trackStage, getFunnel };