Files
hope-events/backend/src/hub/readOnly.js
T
joshuaandClaude Opus 5.5 a7c8148588 Season passes (1.47.3)
A pass sells a ticket at each of several events in a series for one
price. Supervisors set passes up on the Event series page (events and
ticket per event; fixed once sold). Buying on the series page registers
the buyer at every event at once with one checkout (free passes give
tickets straight away; Pay now if online payment isn't possible). The
price is split over the events in proportion to their normal prices:
each registration line is priced at its share and the webhook records
one Payment per event, so cashups stay per event. Pass registrations
aren't repriced, take no coupons and can't be edited. Migration
20261028150000_season_pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:19:58 +02:00

67 lines
3.2 KiB
JavaScript

/**
* Read-only mode while the licence is suspended, revoked or missing (not connected to the Hub):
* no new or edited events, and no *new* activity — registrations, tickets added, payments started
* or recorded, waitlist places, coupons or bundles.
* Everything else keeps working on purpose: scanning tickets for events people already paid for,
* viewing and exporting data, refunds and cancellations, privacy requests, logging in, and the
* settings needed to renew the licence. Payment webhooks for payments already started also still
* arrive (they're mounted before this middleware).
*/
const { licenceStatus } = require('./licence');
const BLOCKED = [
['POST', /^\/api\/events\/?$/],
['PUT', /^\/api\/events\/[^/]+\/?$/],
['POST', /^\/api\/events\/[^/]+\/copy-from\/?$/],
['POST', /^\/api\/series\/?$/],
['PUT', /^\/api\/series\/[^/]+\/?$/],
// Season passes: new or changed passes, and buying one (it makes registrations and payments).
['POST', /^\/api\/season-passes\/?$/],
['PUT', /^\/api\/season-passes\/[^/]+\/?$/],
['POST', /^\/api\/season-passes\/[^/]+\/buy\/?$/],
['POST', /^\/api\/season-passes\/purchases\/[^/]+\/checkout\/?$/],
['POST', /^\/api\/events\/[^/]+\/options\/?$/],
['PUT', /^\/api\/events\/options\/[^/]+\/?$/],
['POST', /^\/api\/events\/options\/[^/]+\/variants\/?$/],
['PUT', /^\/api\/events\/variants\/[^/]+\/?$/],
['POST', /^\/api\/registrations\/?$/],
['POST', /^\/api\/registrations\/manual\/?$/],
['PUT', /^\/api\/registrations\/[^/]+\/options\/?$/],
['POST', /^\/api\/payments\/?$/],
['POST', /^\/api\/payments\/(checkout|yoco-checkout)(\/send)?\/?$/],
['POST', /^\/api\/payments\/pay\/[^/]+\/?$/],
['POST', /^\/api\/waitlist\/?$/],
['POST', /^\/api\/coupons\/?$/],
['POST', /^\/api\/bundles\/event\/[^/]+\/?$/],
// Volunteer rosters: new roles and new people on them (they can carry a free ticket).
['POST', /^\/api\/volunteers\/roles\/[^/]+\/sign-up\/?$/],
['POST', /^\/api\/volunteers\/manage\/events\/[^/]+\/roles\/?$/],
['POST', /^\/api\/volunteers\/manage\/roles\/[^/]+\/assign\/?$/],
];
function isBlocked(method, path) {
return BLOCKED.some(([m, re]) => m === method && re.test(path));
}
async function licenceReadOnly(req, res, next) {
if (!isBlocked(req.method, req.path)) return next();
try {
const status = await licenceStatus();
if (!status.readOnly) return next();
return res.status(403).json({
code: 'LICENCE_READ_ONLY',
message: status.state === 'revoked'
? "This site's licence has been revoked, so nothing new can be added and events can't be changed. Tickets still scan and all data can still be viewed and exported."
: status.state === 'unlicensed'
? "This site has no licence, so nothing new can be added and events can't be changed. A site admin can connect a licence key in Site settings → Licence."
: "This site's licence is suspended, so nothing new can be added and events can't be changed for now. Tickets still scan and all data can still be viewed and exported. Please contact the site's administrator.",
});
} catch {
// Never let a licence check failure take the site down.
return next();
}
}
module.exports = { licenceReadOnly, isBlocked, BLOCKED };