Commit Graph
6 Commits
Author SHA1 Message Date
peter b1832afd18 style: apply ruff format across backend + scripts (line-length 100)
Mechanical, whole-tree `ruff format` pass — no behaviour change. Recorded in
.git-blame-ignore-revs (next commit) so git blame skips it.
2026-08-23 07:43:06 +02:00
peter 3f047bbad1 refactor(config): config/state mutators flush, callers commit (E3 S3, B-core-10)
Restores CLAUDE.md's canonical transaction rule ("the handler commits; helpers
flush; only quota owns its session") on the config path: sysconfig.set_value /
sysconfig.reset and state.set_sync_paused now flush instead of committing, so a
change lands in ONE transaction with the audit row the route records for it —
previously the helper's internal commit split them, and a crash between left the
mutation persisted with no audit trail.

All callers already commit (the config PATCH/DELETE + audit, the sync pause/resume
+ audit, the scheduler maintenance PATCH); the setup wizard, which relied on the
internal commit, gets an explicit db.commit() after its loop. apply_interval is
deliberately left committing — it performs a live APScheduler reschedule that must
follow the DB persist, so it owns its own unit of work (a legitimate exception,
like the download service transitions). New test locks the flush-not-commit
contract (a rollback before the caller commits discards the change). 368 pass.
2026-08-18 23:46:48 +02:00
peter 88c5d5c5cb docs: refresh 7 stale module headers that describe shipped work as "later phase" (E3 S3, B-core-12)
This codebase leans on "why" comments as its architecture doc, so a header that
claims the opposite of the code below it is worse than none. Fix the seven the
review flagged: channels.py (subscribe/unsubscribe shipped), playlists.py +
sync/playlists.py (YouTube two-way sync + write-back shipped), setup.py (drop the
"epic 6c" ref — the wizard steps exist), config.py ("auth._client_ip" ->
"routes.auth._client_ip"), db.py ("routes/plex.py" -> "routes/plex/" after the
package split), sysconfig.py (the "Phase C" note — the watch-sync job that reads
plex_watch_sync_interval_min has landed; clarified it's a boot default, not a knob).
Docs only.
2026-08-18 23:28:25 +02:00
peter ab83aae8bb refactor(api): Pydantic request models for setup & admin routes (R6 S1b)
- setup: SetupAdminIn (KILLS the 3rd named 500 — a non-string password reaching
  argon2/len()), SetupTestEmailIn; setup_config stays a typed dict[str, Any] (a
  genuinely dynamic key→value settings map, not a fixed schema).
- admin: AddInviteIn / SetRoleIn / SetSuspendedIn / AddDemoWhitelistIn. The
  role/status business-rule 400s are unchanged; type errors are now 422.

All 3 ROADMAP-named 500-producers now fixed (channels priority + tag_id, setup
password). Gate: ruff clean, pytest 164 green.
2026-07-26 04:56:55 +02:00
peter 50c710ae9c feat(setup): install-wizard step endpoints (epic 6c, API)
- POST /api/setup/admin (create the first admin: active+verified+admin), /config (persist any
  registry-backed settings — Google creds, SMTP, quota…), /test-email, and /finish (mark
  configured + invalidate the token). All behind require_setup (valid token AND not configured).
- GET /api/setup/info reports secrets_manageable so the wizard hides the Google/SMTP secret steps
  on a box without TOKEN_ENCRYPTION_KEY.
2026-06-21 00:40:54 +02:00
peter ef6c915b12 feat(setup): first-boot detection + setup-mode gating + one-time token (epic 6a)
- app_state gains configured + setup_token_hash (migration 0025); existing installs (any users)
  are marked configured so they never see the wizard, a fresh DB starts in setup mode.
- On first boot the app mints a one-time setup token and logs the wizard URL (only the hash is
  stored); the token gates the setup steps (added in 6c).
- A middleware locks all /api + /auth routes (except /api/setup, /api/version, /healthz, static)
  to 503 until configured, with a DB-free cached fast-path once setup completes.
- GET /api/setup/status tells the SPA whether to show the wizard.
2026-06-21 00:38:47 +02:00