Infrastructure
Semester reset
Handing the app to the next cohort. Do this once, between courses.
Backing up first
The database is one SQLite file. There is no automatic backup and deletion is irreversible, so take a copy before anything else.
From Railway, open a shell on the backend service and copy the file out, or use the export endpoint — which is usually enough, since the votes are the part with research value:
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"https://your-backend.up.railway.app/admin/export?format=csv" -o votes-2026-spring.csv
Locally with Docker:
docker compose cp backend:/data/app.db ./backup-2026-spring.db
Keep both: the CSV is what the analysis consumes, the .db file is the complete record
including users and unresolved questions.
Warning: The export contains pseudonyms and every individual vote. Pseudonymous is not anonymous — if you hold the mapping from pseudonyms to students, treat the export as personal data and store it accordingly. Email addresses are not in the export, but they are in the database file.
What to reset
Decide deliberately; these are separable.
| Keep | Wipe | |
|---|---|---|
| Questions and votes | Never — they are last semester's | Yes, always |
| User accounts | Only if the same students continue | Usually yes |
ADMIN_TOKEN |
— | Yes, if the previous TA is leaving |
JWT_SECRET |
— | Rotate to log everyone out |
| API keys | Yes, unless they were personal | — |
Full wipe
The bluntest and cleanest approach: delete the database file and let the app recreate it empty on next start.
Railway: delete and reattach the volume, or open a shell and remove
/data/app.db plus its -wal and -shm siblings, then restart the service. The schema
is created automatically on boot by initDb.
Local Docker:
docker compose down -v # -v removes the db-data volume
docker compose up --build -d
docker compose exec backend npm run seed # optional test data
Local, no Docker:
rm -f apps/backend/data/app.db
npm run seed
Partial reset: keep accounts, drop questions
If the same students continue, deleting each question from the admin console removes its votes too, leaving users intact. Fine for a handful of questions, tedious for a semester's worth.
For a bulk clear, open the database and run:
DELETE FROM votes;
DELETE FROM smi_questions;
DELETE FROM youtube_suggestions;
DELETE FROM questions;
Order matters — votes and the two source tables reference questions.
Danger: There is no undo. Take the backup from the top of this page first, and verify you can read it before deleting anything.
Rotate the secrets
In the Railway backend service's Variables tab:
ADMIN_TOKEN— the previous TA's admin access dies the moment you change this. Generate something long and random; it is a password, not an identifier.JWT_SECRET— changing it invalidates every student session. Harmless when you have just wiped the accounts anyway.
Then update the Authorization header on all four cron jobs to the new admin token, or
they will start returning 403. This is the step most likely to be forgotten.
Checklist for the new semester
- Backup taken and verified readable.
- Database wiped.
ADMIN_TOKENandJWT_SECRETrotated.- Cron jobs updated with the new token and verified firing — see Cron setup → Verifying.
- Cron schedules checked against the current CET/CEST season.
YOUTUBE_API_KEYstill valid and within quota.RESEND_API_KEYstill valid, and a test password reset actually arrives.- Register a test account end-to-end: vote, resolve, check History.
- Hand over this handbook and the admin token to whoever is taking over.
Generating a good secret
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })
Next
→ Lecture day: turning a semester of votes into the live session.