// Public · Events — the anonymous event surface (EVENTS.md § API surface). // // Phase 14a. Three reads and no writes: the calendar, one event, one arc. // // **Every one of them is a thin pass-through to `eventPublic.model`, and that is // deliberate.** The projection — which fields exist at all on a public entry — is // the security boundary, and it belongs in one file rather than in three // controllers that would each have to remember it. What is left here is the // HTTP: parse the query, map the model's `status` onto a response code, and turn // a thrown read into a 500 rather than a stack trace. // // **A 404 here means "no such public event"** and cannot be told from "no such // slug at all". A draft, an archived definition and an unlisted one answer // identically, which is the whole point: an operator who has not announced // something has not announced its existence either. const events = require('../../../model/events/eventPublic.model') const log = require('../../../utils/logger')('public:events') const fail = (res, err, what) => { log.error(`${what} failed`, { message: err.message }) return res.status(500).json({ message: 'Internal Server Error' }) } const answer = (res, result) => result.ok ? res.json(result) : res.status(result.status || 400).json({ message: result.errors?.[0] || 'Bad Request', errors: result.errors }) async function getCalendar(req, res) { try { const seriesId = req.query.seriesId ? Number(req.query.seriesId) : null if (req.query.seriesId && !Number.isInteger(seriesId)) { return res.status(400).json({ message: 'seriesId must be an integer' }) } const result = await events.calendar({ from: req.query.from || null, to: req.query.to || null, seriesId, }) return answer(res, result) } catch (err) { return fail(res, err, 'public calendar') } } async function getEvent(req, res) { try { // `run` is optional and un-validated beyond being carried through as a // string: the model matches it against this definition's own runs and // ignores anything else, so a garbage value renders the page rather than an // error. See the model's note on why it is not refused. // `userId` is the reader's, when they sent a session (`optionalAuth`): a // module's progress line may show a signed-in reader more than a stranger. const result = await events.event(req.params.slug, { runId: req.query.run || null, userId: req.user && req.user.id != null ? req.user.id : null, }) return answer(res, result) } catch (err) { return fail(res, err, 'public event') } } async function getSeries(req, res) { try { return answer(res, await events.series(req.params.slug)) } catch (err) { return fail(res, err, 'public series') } } module.exports = { getCalendar, getEvent, getSeries }