simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit f52439bb8de182f6348802994952ec99cf4fd6ca
parent e9293bae2480eb0a009d4342300d92a8990db357
Author: Silas Brack <silasbrack@gmail.com>
Date:   Sat, 13 Jun 2026 14:56:02 +0200

docs: add migration guidelines to README

Documents transaction semantics, version bumping, PRAGMA handling,
and testing. Also updates auth description (opaque sessions, not JWT).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
MREADME.md | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 70 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md @@ -7,7 +7,7 @@ A link aggregator built with Rust, Axum, Askama, and SQLite. - **Mechanical sympathy** — match concurrency to hardware. Thread-local SQLite connections, one per core. No mutex contention on the read path. - **Simplicity** — no ORMs, no abstractions. SQL strings, direct function calls, linear control flow. A handler reads top-to-bottom: authenticate, query, render, respond. - **Crash early** — all environment variables are required. Missing config panics on startup, not at request time. No silent defaults. -- **Minimal dependencies** — hand-rolled SMTP, connection pool, and migration system. 9 direct dependencies. +- **Minimal dependencies** — hand-rolled SMTP, connection pool, and migration system. - **No indirection** — no traits "in case we swap later", no generic abstractions over concrete types. ## Architecture @@ -15,7 +15,7 @@ A link aggregator built with Rust, Axum, Askama, and SQLite. - **Axum** async web server with **Askama** compile-time HTML templates - **SQLite** via rusqlite — embedded, no separate database service - **Datastar** v1.0.2 for frontend interactivity (voting, search, real-time comment updates via SSE) -- **Auth**: argon2id password hashing, JWT tokens, email verification via hand-rolled SMTP +- **Auth**: argon2id password hashing, opaque session tokens, email verification via hand-rolled SMTP ### Database @@ -23,7 +23,7 @@ A link aggregator built with Rust, Axum, Askama, and SQLite. - Single writer connection behind a Mutex (SQLite allows one writer at a time) - Blocking threads capped to core count via `max_blocking_threads` - WAL mode, `synchronous=NORMAL`, mmap, prepared statement caching -- BurntSushi-style migrations: numbered `.sql` files in `migrations/`, embedded at compile time via `include_str!` +- All tables use STRICT mode ### Frontend (Datastar) @@ -31,3 +31,70 @@ A link aggregator built with Rust, Axum, Askama, and SQLite. - SSE responses use `text/event-stream` with `datastar-patch-elements` events - Default outer morph mode — include element ID in response HTML - `X-Accel-Buffering: no` header on SSE responses for nginx compatibility + +## Migrations + +Schema migrations live in `migrations/` as numbered SQL files, embedded at compile time via `include_str!`. + +``` +migrations/ + 0001_initial_schema.sql + 0002_email_verification.sql + 0003_strict_tables.sql + 0004_sessions.sql +``` + +### Adding a migration + +1. Create `migrations/NNNN_description.sql` +2. Add `include_str!("../migrations/NNNN_description.sql")` to `src/migrations.rs` + +### Migration file format + +Each migration file owns its own transaction and version bump: + +```sql +-- PRAGMAs that must run outside a transaction go first +PRAGMA foreign_keys=OFF; + +BEGIN IMMEDIATE; + +-- Schema changes here +ALTER TABLE ...; +CREATE TABLE ...; + +-- Bump the version (must match the migration number) +UPDATE migration_version SET version = N; +COMMIT; + +-- Restore PRAGMAs +PRAGMA foreign_keys=ON; +``` + +Rules: +- Every migration must `BEGIN IMMEDIATE` and `COMMIT` +- The version bump (`UPDATE migration_version`) must be inside the transaction — if the migration fails, the version doesn't advance +- PRAGMAs like `foreign_keys=OFF` go before `BEGIN` (they don't take effect inside transactions in some SQLite versions) and must be restored after `COMMIT` +- Once a migration is committed to main, it must never change +- Two branches adding migrations will conflict in `src/migrations.rs` (git catches it) + +### Testing + +`cargo test` verifies: +- All migrations apply cleanly on a fresh database +- Running migrations twice is a no-op (idempotent) +- `PRAGMA foreign_keys` is ON after all migrations complete + +## Setup + +```bash +cp .env.example .env # edit with real values +nix develop +cargo run +``` + +## Deployment + +Deployed on NixOS via an infrastructure flake. Push to `simple-web-app`, update the infra flake (`nix flake update simple-web-app`), push infra. + +Static assets (CSS, JS) are embedded in the binary at compile time. Nginx handles gzip compression.