feat(prefs): user_ai_prefs surface — GET/PUT /prefs, GET /timezones (#126)

- GET /prefs (JWT): current row or empty {voice, library, display_tz,
  updated_at} shape — snake_case, 200 with nulls when no row
- PUT /prefs (JWT): three-state partial update (omitted=unchanged,
  explicit null=clear, value=set) via hand-rolled TriState deserializer —
  serde derive conflates omitted with explicit null on Option<Option<T>>
- display_tz validated against chrono-tz before any write; invalid → 400,
  nothing written. tz_offset_minutes (frozen legacy) preserved verbatim
- GET /timezones (public): chrono_tz::TZ_VARIANTS — exactly the domain
  Tz::from_str accepts, so picker list and validation can't drift
- No migration; existing columns only. capture_prefs (POST /reels) untouched
- 14 tests: 3 GET /prefs, 7 PUT /prefs (incl. auth-gate), 2 /timezones,
  2 pure merge_patch. Full suite 1436 passed / 0 failed
This commit is contained in:
Cameron Cordes committed 2026-09-27 14:40:30 -04:00
1 parent 7f348022d4
commit 2258a0eab5
3 files changed
+732

No files matched your search

+1
View File
@@ -32,6 +32,7 @@ pub mod otel;
pub mod parsers;
pub mod perceptual_hash;
pub mod personas;
pub mod prefs;
pub mod service;
pub mod state;
pub mod tags;