Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61ce138447 | ||
|
|
b28c8a8876 | ||
|
|
31aa787b17 | ||
|
|
51992814d2 | ||
|
|
88d9276960 |
@@ -7,6 +7,30 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.52.3] - 2026-11-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Licence read-only mode works again.** The read-only guard (`src/hub/readOnly.js`) only listed
|
||||
v1 paths, and every screen has used `/api/v2` since 1.51.15 — so a suspended, revoked or
|
||||
unlicensed site could still create and change events, take registrations and start payments.
|
||||
The v2 successors of every blocked v1 route are now blocked too (answered as `READ_ONLY` with
|
||||
reason `LICENCE_READ_ONLY`). A test checks each blocked v2 path is a real route in the OpenAPI
|
||||
document, so a renamed route can't slip out of the list.
|
||||
|
||||
## [1.52.2] - 2026-11-02
|
||||
|
||||
### Changed
|
||||
|
||||
- **`scripts/deploy.sh` stops before touching anything when files in the install folder were
|
||||
changed or added by hand**, and lists them. A setup wizard run on one server had added files to
|
||||
`backend/src` that the backend build picked up, so the deploy failed halfway and rolled back.
|
||||
Ignored files (`.env`, uploads, backups, `ecosystem.config.js`) never count.
|
||||
`ALLOW_LOCAL_CHANGES=1` deploys anyway.
|
||||
- `bash scripts/deploy.sh --check` runs only that check (no backup, fetch, build or reload). The
|
||||
script can also be run from a copy outside the install (e.g. `/tmp/deploy.sh`, from the folder
|
||||
you're in), so an older install can check before updating — see DEPLOYMENT.md, "Updating".
|
||||
|
||||
## [1.52.1] - 2026-11-02
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -311,6 +311,24 @@ and goes back to the previous version if anything fails first. `backend/.env`,
|
||||
`frontend/.env.production`, `ecosystem.config.js` and the nginx/frpc configs are not in git, so
|
||||
updates keep your changes from this guide.
|
||||
|
||||
Since 1.52.2 it first checks for files in the install folder that were changed or added by hand
|
||||
(e.g. by a setup wizard run on the server) and stops, listing them, before it backs anything up
|
||||
or checks anything out: they would be built into the release, or block the checkout. Move added
|
||||
files out of the folder and put changed ones back with `git checkout -- <file>`, or deploy anyway
|
||||
with `ALLOW_LOCAL_CHANGES=1`. To check without deploying:
|
||||
|
||||
```bash
|
||||
sudo -u hopeevents -H bash scripts/deploy.sh --check
|
||||
```
|
||||
|
||||
An install still on an older release can run the new check without updating anything — fetch the
|
||||
script on its own and run it from the install folder:
|
||||
|
||||
```bash
|
||||
git fetch -q origin && git show origin/main:scripts/deploy.sh > /tmp/deploy.sh
|
||||
bash /tmp/deploy.sh --check
|
||||
```
|
||||
|
||||
### Upgrading Node
|
||||
|
||||
Node 20 stopped getting security fixes in April 2026; Cross Events runs on Node 24 LTS (supported
|
||||
|
||||
@@ -12,6 +12,17 @@ Write for church admins, not developers: no environment variables, endpoints or
|
||||
developer log is CHANGELOG.md. A tagged version without a section here shows "No details given".
|
||||
-->
|
||||
|
||||
## 1.52.3
|
||||
|
||||
- **A licence check fix.** When a site's licence has run out or isn't connected, the site is
|
||||
meant to go read-only (tickets still scan and all data can still be viewed and exported). Since
|
||||
1.51.15 that wasn't being applied; it is again. Sites with an active licence see no change.
|
||||
|
||||
## 1.52.2
|
||||
|
||||
- **Safer updates.** If files on your server were changed by hand, an update now stops straight
|
||||
away and says which files, instead of failing part-way. Nothing else changes.
|
||||
|
||||
## 1.52.1
|
||||
|
||||
- **Better documentation for developers.** If someone builds on your Cross Events site (for example
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "event-management-backend",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "event-management-backend",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@cj-tech-master/excelts": "^10.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "event-management-backend",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"description": "Event Management System Backend",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
const { licenceStatus } = require('./licence');
|
||||
|
||||
// v1 paths (until 2.0 removes them) and their /api/v2 successors (1.52.3: the screens have used v2
|
||||
// since 1.51.15, so without these a suspended or unlicensed site wasn't read-only any more).
|
||||
const BLOCKED = [
|
||||
['POST', /^\/api\/events\/?$/],
|
||||
['PUT', /^\/api\/events\/[^/]+\/?$/],
|
||||
@@ -38,6 +40,32 @@ const BLOCKED = [
|
||||
['POST', /^\/api\/volunteers\/roles\/[^/]+\/sign-up\/?$/],
|
||||
['POST', /^\/api\/volunteers\/manage\/events\/[^/]+\/roles\/?$/],
|
||||
['POST', /^\/api\/volunteers\/manage\/roles\/[^/]+\/assign\/?$/],
|
||||
|
||||
// /api/v2 — the same actions.
|
||||
['POST', /^\/api\/v2\/events\/?$/],
|
||||
['PATCH', /^\/api\/v2\/events\/[^/]+\/?$/],
|
||||
['POST', /^\/api\/v2\/events\/[^/]+\/copy-from\/?$/],
|
||||
['POST', /^\/api\/v2\/series\/?$/],
|
||||
['PATCH', /^\/api\/v2\/series\/[^/]+\/?$/],
|
||||
['POST', /^\/api\/v2\/season-passes\/?$/],
|
||||
['PATCH', /^\/api\/v2\/season-passes\/[^/]+\/?$/],
|
||||
['POST', /^\/api\/v2\/season-passes\/[^/]+\/buy\/?$/],
|
||||
['POST', /^\/api\/v2\/season-pass-purchases\/[^/]+\/checkout\/?$/],
|
||||
['POST', /^\/api\/v2\/events\/[^/]+\/ticket-options\/?$/],
|
||||
['PATCH', /^\/api\/v2\/ticket-options\/[^/]+\/?$/],
|
||||
['POST', /^\/api\/v2\/ticket-options\/[^/]+\/variants\/?$/],
|
||||
['PATCH', /^\/api\/v2\/variants\/[^/]+\/?$/],
|
||||
['POST', /^\/api\/v2\/registrations\/?$/],
|
||||
['POST', /^\/api\/v2\/registrations\/manual\/?$/],
|
||||
['PUT', /^\/api\/v2\/registrations\/[^/]+\/items\/?$/],
|
||||
['POST', /^\/api\/v2\/payments\/?$/],
|
||||
['POST', /^\/api\/v2\/registrations\/[^/]+\/(checkout|payment-link(\/send)?)\/?$/],
|
||||
['POST', /^\/api\/v2\/payment-links\/[^/]+\/?$/],
|
||||
['POST', /^\/api\/v2\/waitlist\/?$/],
|
||||
['POST', /^\/api\/v2\/coupons\/?$/],
|
||||
['POST', /^\/api\/v2\/events\/[^/]+\/bundles\/?$/],
|
||||
['POST', /^\/api\/v2\/volunteer-roles\/[^/]+\/(sign-up|assign)\/?$/],
|
||||
['POST', /^\/api\/v2\/events\/[^/]+\/volunteer-roles\/?$/],
|
||||
];
|
||||
|
||||
function isBlocked(method, path) {
|
||||
|
||||
@@ -105,6 +105,48 @@ describe('read-only mode', () => {
|
||||
['POST', '/api/users/login'],
|
||||
['POST', '/api/reports/pdf'],
|
||||
])('%s %s keeps working', (m, p) => expect(isBlocked(m, p)).toBe(false));
|
||||
|
||||
// 1.52.3: the screens use /api/v2, so its routes must be blocked too.
|
||||
test.each([
|
||||
['POST', '/api/v2/events'],
|
||||
['PATCH', '/api/v2/events/ev_1'],
|
||||
['POST', '/api/v2/events/ev_1/copy-from'],
|
||||
['POST', '/api/v2/events/ev_1/ticket-options'],
|
||||
['PATCH', '/api/v2/ticket-options/opt_1'],
|
||||
['PATCH', '/api/v2/variants/var_1'],
|
||||
['POST', '/api/v2/registrations'],
|
||||
['POST', '/api/v2/registrations/manual'],
|
||||
['PUT', '/api/v2/registrations/abc/items'],
|
||||
['POST', '/api/v2/registrations/abc/checkout'],
|
||||
['POST', '/api/v2/registrations/abc/payment-link/send'],
|
||||
['POST', '/api/v2/payments'],
|
||||
['POST', '/api/v2/payment-links/chk_1'],
|
||||
['POST', '/api/v2/waitlist'],
|
||||
['POST', '/api/v2/season-passes/p1/buy'],
|
||||
['POST', '/api/v2/volunteer-roles/r1/sign-up'],
|
||||
])('v2 %s %s is blocked', (m, p) => expect(isBlocked(m, p)).toBe(true));
|
||||
|
||||
test.each([
|
||||
['POST', '/api/v2/checkin/QR123'],
|
||||
['GET', '/api/v2/events'],
|
||||
['PUT', '/api/v2/events/ev_1/notify-recipients'],
|
||||
['POST', '/api/v2/registrations/abc/cancel'],
|
||||
['POST', '/api/v2/payments/refunds'],
|
||||
['POST', '/api/v2/me/privacy/requests'],
|
||||
['POST', '/api/v2/auth/login'],
|
||||
])('v2 %s %s keeps working', (m, p) => expect(isBlocked(m, p)).toBe(false));
|
||||
|
||||
test('every blocked v2 path is a real v2 route', () => {
|
||||
const doc = require('../../frontend/src/lib/api-v2/openapi.json');
|
||||
const routes = Object.entries(doc.paths).flatMap(([p, ops]) =>
|
||||
Object.keys(ops).map(m => [m.toUpperCase(), `/api/v2${p.replace(/\{[^}]+\}/g, 'x1')}`]));
|
||||
const { BLOCKED } = require('../src/hub/readOnly');
|
||||
const unmatched = BLOCKED
|
||||
.filter(([, re]) => re.source.startsWith('^\\/api\\/v2'))
|
||||
.filter(([m, re]) => !routes.some(([rm, rp]) => rm === m && re.test(rp)))
|
||||
.map(([m, re]) => `${m} ${re}`);
|
||||
expect(unmatched).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pasting the Hub public key', () => {
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# Handover — where things stand and how to carry on
|
||||
|
||||
Last updated: 2026-11-02, after **1.52.1** (and Hub 1.5.0). The plan itself is in
|
||||
Last updated: 2026-11-02, after **1.52.2** (and Hub 1.5.0). The plan itself is in
|
||||
[ROADMAP-v2.md](./ROADMAP-v2.md) (decisions table at the end); the previous round of
|
||||
features is in [FEATURES-ROUND-1.md](./FEATURES-ROUND-1.md).
|
||||
|
||||
@@ -14,6 +14,7 @@ encrypted settings unreadable or break existing installs.
|
||||
|
||||
| Version | What |
|
||||
|---------|------|
|
||||
| 1.52.2 | `scripts/deploy.sh` stops first (listing them) when files in the install were changed or added by hand; `--check` runs only that; runs from a copy outside the install too; `ALLOW_LOCAL_CHANGES=1` overrides |
|
||||
| 1.52.1 | **API documentation complete:** every v2 route documents its body, answer and parameters — the ~300 answered by v1 handlers through `documented` shapes (`src/v2/docs.ts`), shown but not enforced, logged outside production when they don't fit; empty bodies say "No body needed". Test `tests/v2Docs.test.ts` |
|
||||
| 1.52.0 | **Urgent fix + Corrections:** required attendee forms no longer leave paid door/manual registrations without tickets — popup with "Fill in later" (`Registration.formWaivedAt`, `POST /api/v2/registrations/{id}/form-later`); one form per person on picked sessions; forms with no questions don't hold tickets; kiosk asks for the forms the server says are missing. Admin **Corrections** page (`src/services/corrections.ts`, `/api/v2/corrections/…`): issue missing tickets, pay later (`ticketsReleasedAt`), make it free (`complimentaryDiscount`), check in after the event. Migration `20261102120000_corrections`. e2e `e2e/corrections.spec.ts` |
|
||||
| 1.51.17 | **Security:** forgot-password and sign-in don't reveal whether an account exists (same answer, status and timing); forgot-password rate-limited |
|
||||
@@ -128,7 +129,7 @@ Languages (Afrikaans, §10) and Wallet passes (§7) are removed from scope (2026
|
||||
The backend declares `engines.node >= 22`. pg-boss 11 (1.51.7) runs in its own schema,
|
||||
`pgboss_v11` — it can't migrate pg-boss 10's `pgboss` schema; there's no `archive` table any more
|
||||
(finished runs stay in `job` until they expire).
|
||||
- **Deploying:** `bash scripts/deploy.sh [tag]` on the server (no tag = latest main). Each install
|
||||
- **Deploying:** `bash scripts/deploy.sh [tag]` on the server (no tag = latest main). It stops first if files in the install were changed or added by hand (`--check` to only check; `ALLOW_LOCAL_CHANGES=1` to go ahead anyway). Each install
|
||||
keeps its own `ecosystem.config.js` (PM2 app names, port), which is not in git. The script
|
||||
runs from a temp copy of itself and rolls back on failure before the PM2 reload. It was
|
||||
tested by running it against a throwaway clone with npm/pm2 stubbed: across the tracked →
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "hope-events-frontend",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hope-events-frontend",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@hookform/resolvers": "^5.2.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hope-events-frontend",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "hope-events",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hope-events",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.14.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hope-events",
|
||||
"version": "1.52.1",
|
||||
"version": "1.52.3",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev:backend": "cd backend && npm run dev",
|
||||
|
||||
+39
-1
@@ -6,20 +6,34 @@
|
||||
# Usage (on the server, from the repo folder):
|
||||
# bash scripts/deploy.sh # the latest main
|
||||
# bash scripts/deploy.sh v1.37.3 # a tagged release
|
||||
# bash scripts/deploy.sh --check # only check for files changed by hand (changes nothing)
|
||||
#
|
||||
# PM2 uses this install's own ecosystem.config.js (app names, ports). It isn't in git, so
|
||||
# deploys never change it; the first deploy creates it from ecosystem.config.example.js.
|
||||
#
|
||||
# A deploy stops before touching anything when files in the repo folder were changed or added
|
||||
# by hand: they'd be built into the release (or block the checkout). ALLOW_LOCAL_CHANGES=1
|
||||
# deploys anyway. Ignored files (.env, uploads, backups, the PM2 config) never count.
|
||||
set -Eeuo pipefail
|
||||
|
||||
# The checkout below replaces this file, and bash reads a script as it runs — so run from a
|
||||
# copy that can't change underneath us.
|
||||
if [ -z "${HOPE_DEPLOY_COPY:-}" ]; then
|
||||
DIR="${HOPE_DEPLOY_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
# Run from a copy outside the repo (e.g. downloaded to /tmp): the folder you're in.
|
||||
if [ ! -d "$DIR/.git" ] || [ ! -d "$DIR/backend" ]; then DIR="$PWD"; fi
|
||||
COPY="$(mktemp)"
|
||||
cp "$0" "$COPY"
|
||||
HOPE_DEPLOY_COPY=1 HOPE_DEPLOY_DIR="$(cd "$(dirname "$0")/.." && pwd)" exec bash "$COPY" "$@"
|
||||
HOPE_DEPLOY_COPY=1 HOPE_DEPLOY_DIR="$DIR" exec bash "$COPY" "$@"
|
||||
fi
|
||||
|
||||
cd "$HOPE_DEPLOY_DIR"
|
||||
if [ ! -d .git ] || [ ! -d backend ]; then
|
||||
echo "!!> $PWD isn't a Cross Events install. Run this from the install's folder (e.g. /var/www/hope-events)."
|
||||
exit 1
|
||||
fi
|
||||
CHECK_ONLY=0
|
||||
if [ "${1:-}" = "--check" ]; then CHECK_ONLY=1; shift; fi
|
||||
TARGET="${1:-main}"
|
||||
PREVIOUS="$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD)"
|
||||
|
||||
@@ -51,6 +65,30 @@ checkout() {
|
||||
restore_pm2_config
|
||||
}
|
||||
|
||||
# Files changed or added by hand (anything git reports, apart from this install's PM2 config,
|
||||
# which releases before 1.37.4 tracked). Ignored files — .env, uploads, backups — aren't listed.
|
||||
local_changes() {
|
||||
git status --porcelain --untracked-files=all | awk -v keep="$PM2_CONFIG" 'substr($0, 4) != keep'
|
||||
}
|
||||
|
||||
CHANGES="$(local_changes)"
|
||||
if [ -n "$CHANGES" ]; then
|
||||
echo "!!> Files in $PWD were changed or added by hand:"
|
||||
echo "$CHANGES" | sed 's/^/ /'
|
||||
echo " A release would build them in (or refuse to replace them), so the deploy can fail."
|
||||
echo " Move added files (??) somewhere outside this folder, and put changed ones ( M) back with"
|
||||
echo " \"git checkout -- <file>\" — after checking nothing in them is needed."
|
||||
if [ "$CHECK_ONLY" = 1 ]; then exit 1; fi
|
||||
if [ "${ALLOW_LOCAL_CHANGES:-}" != 1 ]; then
|
||||
echo " Nothing was changed. To deploy anyway: ALLOW_LOCAL_CHANGES=1 bash scripts/deploy.sh $TARGET"
|
||||
exit 1
|
||||
fi
|
||||
echo " ALLOW_LOCAL_CHANGES=1 — deploying anyway."
|
||||
elif [ "$CHECK_ONLY" = 1 ]; then
|
||||
echo "==> No files changed by hand in $PWD — ready to deploy."
|
||||
fi
|
||||
if [ "$CHECK_ONLY" = 1 ]; then exit 0; fi
|
||||
|
||||
echo "==> Deploying $TARGET (currently $PREVIOUS)"
|
||||
|
||||
# Node 20 reached end of life in April 2026 (no more security fixes). Releases still run on it,
|
||||
|
||||
Reference in New Issue
Block a user