2 Commits
Author SHA1 Message Date
Juan Denis 2365a32408 refactor(models): one JsonColumnMixin instead of 24 hand-rolled copies
Every model with a JSON-in-Text column hand-rolled the same try/except
json.loads pair — get_details/get_context/get_headers/get_payload and a
dozen more, each a place the next fix would have to land N times. The
mixin owns the mechanics (empty-is-default, corrupt-is-default so a
hand-edited row never 500s an endpoint, optional shape check,
falsy-on-write); the per-model parts — accessor name, column, default —
stay per-model.

Conversion is behaviour-preserving: identical fallbacks, identical
on-write empty forms (NULL vs '[]' vs '{}'). Deliberately NOT converted:
accessors that let json.loads raise on corrupt data (collapsing those
would be a behaviour change, not a consolidation), and inline parses in
to_dict (per-entity serialization choice). A handful of 'except
Exception' reads narrow to the dominant (JSONDecodeError, TypeError) —
the only exceptions json.loads raises for str input.
2026-08-17 21:43:59 -04:00
Juan Denis 47c92e0571 feat(auth): WebAuthn / Passkey support
- Add webauthn dependency.
- Add PasskeyCredential model and Alembic migration 019_passkey_credentials.
- Add PasskeyService for registration/authentication challenge flow.
- Add auth API endpoints:
  - POST /auth/passkeys/options/register
  - POST /auth/passkeys/register
  - GET /auth/passkeys
  - DELETE /auth/passkeys/<id>
  - POST /auth/passkeys/options/authenticate
  - POST /auth/passkeys/authenticate
- Expose passkey_enabled flag in User.to_dict().
2026-06-19 01:33:45 -04:00