After the sanitize_payload order fix, account_id was being re-injected
into data_to_insert for models that explicitly list account_id in
fields_to_exclude_from_db (e.g. event_badge, event_device). Those tables
have no account_id column, causing INSERT failures.
Guard the post-sanitize account_id injection in both api_crud_v3.py and
api_crud_v3_nested.py by checking fields_to_exclude_from_db first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In the nested POST handler (api_crud_v3_nested.py), sanitize_payload was
running before model instantiation. For secondary FK fields like
event_badge_template_id, sanitize_payload resolved the random string →
integer, then the model's root_validator stripped the integer back to None
(Vision ID anti-leakage guard). Only the parent FK survived because it was
explicitly re-injected after serialization.
Fix: moved sanitize_payload to run on data_to_insert after serialization,
matching the flat V3 POST pattern (api_crud_v3.py). Also moved account_id
injection to after sanitize_payload, fixing a latent bug where account_id
was silently written as NULL on non-bypass auth.
Adds regression test to test_e2e_v3_demo_parity.py that creates an
event_badge via nested POST with event_badge_template_id and verifies the
field is non-None in the response.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All create_update_*_v4 functions for event_badge, event_person,
event_person_profile, event_presenter, and event_presentation were
calling sql_update without record_id. Vision ID models use Optional[str]
IDs with a root_validator that strips integer values, so the serialized
dict contained no id key and sql_update could not identify the row.
Fix: pass record_id=<integer_id> explicitly to sql_update in all five
functions. Also fix walrus-operator false-negative: None return from
sql_update (0 rows affected — record unchanged) is not an error and
should not abort sub-object cascade; use explicit `is False` check.
Also broadens Axonius badge_type_code mapping to substring match so
future ticket name variants still resolve correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add tests/e2e/test_e2e_jitsi_token.py: verifies moderator/attendee claims,
room isolation, input validation, and exp claim correctness
- Update Jitsi section comment in api.py with actionable secret rotation TODO
(must update JWT_APP_SECRET here AND in dgr_zone_jitsi .env, then restart
prosody + jicofo)
Removed the 403 guard that blocked non-moderators. is_moderator is
already passed through to the token payload, so participants get
"moderator": false as expected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uncommented and completed access_key + referrer handling in
lookup_site_domain_fqdn() and the GET /site/domain/fqdn/{fqdn} route.
These routes are disabled in registry.py and not currently active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New router: /v3/action/user/ (api_v3_actions_user.py)
- POST /authenticate — credentials in body (not query params; security fix)
- POST /verify_password
- POST /{user_id}/change_password — optional current-password verification
- GET /{user_id}/new_auth_key
- GET /{user_id}/email_auth_key_url
Registered in registry.py under /v3/action/user with V3 AccountContext auth.
Bug fixes (from audit in previous session):
- user.py: fix broken @router.get decorator (authenticate was unreachable)
- user.py + user_methods.py: fix AttributeError id_random → id (Vision ID)
- user_models.py: add fields_to_exclude_from_db to User_New_Base; narrow
collision prevention to self-reference IDs only
- user_models.py: pre-inject hashed password in root_validator(pre=True) so
exclude_unset=True in CRUD POST handler includes it (was writing NULL)
- api_crud_v3.py: move sanitize_payload + account_id injection to after
model validation (fixes FK integer collision with Vision ID constraints)
Docs: GUIDE__AE_API_V3_for_Frontend.md — new Section 7 with full migration
table (legacy → V3), request/response docs for all 5 action endpoints,
and V3 CRUD search equivalents for the 3 lookup routes.
Tests: tests/e2e/test_e2e_v3_user_action_routes.py — 19 tests, 19/19 pass.
Legacy tests/e2e/test_e2e_v3_user_auth_routes.py — 22/22 still pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extracted apply_vision_id_fix() helper to lib_api_crud_v3.py — single
source of truth for the fix that ensures {obj_type}_id in responses is
always the random string, never the DB integer.
- Applied to all response-returning paths in api_crud_v3.py:
GET single, GET list, POST search, POST create, PATCH update.
- Applied to all response-returning paths in api_crud_v3_nested.py:
GET child list, POST search, POST create, GET single child, PATCH child.
- Removed duplicate get_child_obj and patch_child_obj route handlers in
api_crud_v3_nested.py — FastAPI silently routes to only the first
matching handler, so the second definitions were unreachable dead code.
Covers all 23 V3 CRUD models still using the old integer-alias pattern.
The archive_content model was already migrated to Vision IDs; this fix
ensures every other model gets correct responses without individual migration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace integer `id` (alias archive_content_id) with Vision string fields:
`id: Optional[str]` and `archive_content_id: Optional[str]` — both always
hold the random string ID, never the DB integer.
- Add `root_validator(pre=True)` (map_v3_ids) that maps id_random /
archive_content_id_random → id and archive_content_id, with collision
prevention to reject any integer that arrives in these fields.
- Remove old `archive_content_id_lookup` integer validator (superseded by
sanitize_payload + root_validator).
- Keep `id_random` (alias archive_content_id_random) in responses for
backward compatibility; add id, archive_content_id, id_random to
fields_to_exclude_from_db so they never appear in INSERT/UPDATE payloads.
Generic CRUD layer safety net (post_obj + post_child_obj):
- After building resp_data on create, swap any integer {obj_type}_id with
the corresponding {obj_type}_id_random value — catches models not yet
migrated to Vision IDs.
- Fix return_obj=False fallback to return obj_id as the random string.
Docs: add Section 3D to GUIDE__AE_API_V3_for_Frontend.md documenting the
Vision ID convention — {obj_type}_id is always the random string; the
_id_random suffix is a legacy artifact that frontend code should phase out.
Fixes: POST /v3/crud/archive/{id}/archive_content/ returning integer ID,
breaking the subsequent PATCH flow (422 min_length validation failure).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ported the legacy '/hosted_file/hash/{hash}' endpoint to the V3 actions router.
The new endpoint '/v3/action/hosted_file/hash/{hosted_file_hash}' supports:
- ID Vision: returns random string IDs instead of internal integers
- Local Check: verifies physical file existence on disk (check_for_local=True)
- Deduplication: enables frontend to check for existing files before upload
Also added PROJECT document for AE Hosted Files migration tracking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Newer gunicorn patch releases added _get_control_socket_path() which
crashes with TypeError when control_socket is None. Pin to the working
version until the gunicorn config fix propagates everywhere.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tags remaining live-but-deprecated routes so every call logs a warning,
giving visibility before the next round of removals.
- registry.py: add DeprecationParams to importing and user routers
- api.py: add DeprecationParams to /request_jwt and /temp_token individually
- user.py: inherits deprecation warning via registry router-level dependency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Disabled legacy routes that are superseded by V3 equivalents. Code is
commented out (not deleted) pending final verification and cleanup pass.
- registry.py: remove sql, lookup (/lu), websockets, websockets_redis;
clean up dead imports (contact, event_person, etc.)
- data_store.py: comment out legacy CRUD and code-lookup endpoints;
keep V3 code-lookup routes active; add TODO for action path rename
- api.py: comment out Api_Base CRUD, get_id (internal ID leak),
and sql_test (debug) endpoints
- aether_cfg.py: comment out legacy Flask cfg endpoint
- user.py: comment out legacy user endpoints
- util_email.py: minor cleanup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents the root cause of the timezone collapse bug and how to avoid it
in future data imports. Covers:
- group as the dedup identity key (not a display label), per lookup type
- correct way to add/update/remove account and object overrides
- hard invariant for time_zone: group must equal name
- verification query to catch bad seed data before it ships
- frontend keying guidance: use group, not id or name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverts the PARTITION BY name change — group is the correct dedup key.
Partitioning by name broke country deduplication (two US records both
survived, causing Svelte each_key_duplicate on alpha_2_code='US').
Root cause is bad seed data in lu_v3_time_zone: group='United States'
for 13 US/* zones and group='Europe' for 63 Europe/* zones instead of
group=name. A separate DB UPDATE is required to fix those rows.
Tests updated to assert:
- No duplicate alpha_2_code in country list (PARTITION BY group regression)
- All 13 US/* and Europe/* spot-check zones present (pending DB data fix)
- priority-only timezone count == 72 (pending DB data fix)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ROW_NUMBER() was partitioning by `group`, collapsing all 12 US/* timezones
(which share group="United States") down to a single record. Partitioning
by `name` correctly deduplicates by timezone identity while still preserving
the object > account > global override hierarchy.
Priority-only list now returns the expected 72 entries. Adds a regression
test asserting all 12 US/* timezones are present in the full list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers the fix from 308a7f2 — verifies that MariaDB error 1364
is classified as database_schema and the field name is extracted
into a readable message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parses the field name from the MariaDB error and returns a clear
"Schema mismatch: column 'X' is NOT NULL..." message instead of the
raw DB string. Consistent with how 1054/1146 (unknown column/table)
are already handled.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>