Fold the pre-deploy review findings into the calendar cutover
A fresh-context review before deploying found two things that would have broken production, both in the runbook rather than the code. The deploy order named only migration 003. Production has none of the three - 001 and 002 were only ever applied to the dev database - and the new API reads the columns they add on every request, so following it literally would have 500'd every calendar call including the anonymous feed the public website uses. Step 4 now carries a numbered checklist with a verification query. APP_ORIGINS replaces the code's default list rather than adding to it, so naming calendar.nachklang.art in DEFAULT_APP_ORIGINS is not enough if that variable is set on the vhost - and its failure mode is the quiet one the config already warns about, where everything works except sign-out. Added to the same checklist. Also from the review: The two operands of the read guard on /json/next and /ical were swapped so the password check short-circuits first. They are side-effect free, so the order was free - but the old one put an admin-database query in front of the public feed for any caller holding a .nachklang.art cookie, which is a dependency that feed has never had. Two tests now assert the admin database is not consulted at all. /:calendar/json/next had no route-level test, despite being the endpoint the public website actually calls and the property named as load-bearing. Covered now, along with the rest of its credential matrix. Migrations 001 and 002 gained IF NOT EXISTS. They are applied by hand with no tracking table, so a partial re-run should be a no-op rather than an error that aborts the rest of the paste. Verified by applying all three twice to a throwaway container and diffing against the dev schema. Swagger: two descriptions still claimed authentication was required where the public calendar needs none, the calendar enum omitted `birthdays`, and a `createdBy` request-body field was documented and read but never persisted - misleading in a way that suggests a client can set authorship. Removed. The CORS comment describing the calendar's query-parameter sessions is no longer true and was rewritten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
# Migrating the Calendar domain onto the admin identity module
|
||||
|
||||
Status: **steps 1-4 done** (2026-09-06), step 2 dropped by decision, part of step 5 brought
|
||||
forward. Only step 5, the removal of the legacy path, is left.
|
||||
Status: **steps 1-4 implemented 2026-09-06, not yet merged or deployed.** Step 2 dropped by
|
||||
decision, part of step 5 brought forward. Only step 5, the removal of the legacy path, is
|
||||
left to write.
|
||||
|
||||
> Read the deploy checklist under step 4 before applying anything. "Done" below means the
|
||||
> code exists on a branch, **not** that production has it - and in particular production has
|
||||
> none of the three migrations.
|
||||
|
||||
Written 2026-09-05 alongside the admin module (step 2 of `docs/plan-admin-auth.md` in the
|
||||
nachklang-admin repo), which deliberately left the calendar alone. Steps 1-4 of that plan
|
||||
@@ -116,11 +121,41 @@ Each step is meant to leave production working on its own.
|
||||
`test/calendar/credentials.service.test.ts`, the routes themselves in
|
||||
`test/calendar/events.router.test.ts` - so this cannot regress quietly.
|
||||
|
||||
**Deploy order:** migration 003, then the API, then the calendar frontend. The frontend is
|
||||
broken between the last two (its old bundle sends query credentials the new API ignores),
|
||||
so pick a quiet moment. Production also needs `calendar.nachklang.art` in the admin app's
|
||||
`NEXT_PUBLIC_ALLOWED_REDIRECT_ORIGINS`, which is a **build-time** value: a rebuild, not a
|
||||
restart.
|
||||
### Deploy checklist
|
||||
|
||||
Production has **none** of the three migrations: 001 and 002 were only ever applied to the
|
||||
dev database. The API build below selects `created_by_user_id` and `created_by_name` on
|
||||
every read, so deploying it against a database missing them fails every calendar request
|
||||
including the anonymous public feed the website uses. In order:
|
||||
|
||||
1. **Apply `sql/calendar/001`, `002`, `003`, in that order**, against `CALENDAR_DB`. All
|
||||
three are re-runnable, so applying one that is already applied is a no-op. Verify
|
||||
before continuing:
|
||||
`SHOW COLUMNS FROM events LIKE '%by_user%'; SHOW COLUMNS FROM events LIKE '%by_name%';`
|
||||
- four rows across the two tables, and `created_by_id` nullable.
|
||||
2. **Check `APP_ORIGINS` on the API vhost.** `calendar.nachklang.art` is in the code's
|
||||
default list, but the environment variable *replaces* that list rather than adding to
|
||||
it - so if it is set at all (the tickets/feedback cutover may have set it), append
|
||||
`https://calendar.nachklang.art` or the calendar's sign-out will 403 while everything
|
||||
else works. That is the failure mode the comment in `admin.config.ts` warns about.
|
||||
3. **Deploy the API.**
|
||||
4. **Deploy the calendar frontend immediately after.** Do not leave a gap - see below.
|
||||
5. **Re-run 002's two `UPDATE` statements.** Between step 1 and step 3 the old API was
|
||||
still writing `created_by_id` with no snapshot; those few rows would otherwise lose
|
||||
their author at step 5.
|
||||
6. **Rebuild the admin app** if `NEXT_PUBLIC_ALLOWED_REDIRECT_ORIGINS` does not already
|
||||
contain `https://calendar.nachklang.art`. It is a **build-time** value, so a restart
|
||||
does nothing.
|
||||
|
||||
**The window between steps 3 and 4 does not look broken, which is the danger.** The old
|
||||
Angular bundle starts by calling `POST /calendar/users/checkSessionValid`, and those
|
||||
legacy routes are untouched - so it still succeeds and the page renders as signed in. What
|
||||
the user then sees is an empty event table and saves that silently do nothing. It looks
|
||||
like the calendar lost its data, not like a deploy in progress. Keep the gap to minutes,
|
||||
or take the frontend offline for it.
|
||||
|
||||
**One-way door:** any iCal subscription whose URL carries `?sessionId=&sessionKey=` rather
|
||||
than `?password=` stops working permanently. The shared-password URLs are unaffected.
|
||||
5. **Drop the legacy path.** Remove `users.service.ts`'s session handling, the `sessions`
|
||||
table, `created_by_id`, and the legacy half of the step 3 read (the `users` join and its
|
||||
`legacy_*` aliases - the snapshot fallback stays, it is what makes dropping the table
|
||||
|
||||
Reference in New Issue
Block a user