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 eventIds = String(req.query.eventIds || '').split(',').map(s => s.trim()).filter(Boolean).slice(0, 50); if (eventIds.length === 0) { res.status(400); throw new Error('Choose at least one event'); } const events = await prisma.event.findMany({ where: { id: { in: eventIds } }, select: { id: true, title: true } }); 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 };