feat(tradein/audit): request-audit middleware + estimate_request logging → user_events #2519

Merged
lekss361 merged 1 commit from feat/tradein-user-events-capture into main 2026-07-13 20:09:22 +00:00
Owner

Что

Серверный слой захвата событий для фич 2 (учёт входов/IP + что ищут) и базы фичи 3 (поведенческая аналитика). Пишет в user_events (миграция 184).

Как

  • app/services/user_events.py (new):
    • record_event(...) — синхронный INSERT в свою SessionLocal() (decoupled от tx запроса), CAST(:ip AS inet)/uuid/jsonb, никогда не raises (сбой аудита не должен ронять запрос).
    • schedule_event(...) — fire-and-forget: в async-контексте уводит запись в threadpool (asyncio.to_thread) с done-callback (не блокирует event-loop, не теряет исключения); в sync-контексте (тесты) — inline.
    • should_log_login(...) — in-process best-effort дедуп: login-событие максимум раз в сутки на (username, ip, user_agent). Рестарт сбрасывает set (недо-дедуп, не потеря; отчёты всё равно через DISTINCT).
  • app/core/request_audit.py (new) — RequestAuditMiddleware: после call_next для каждого аутентифицированного /api/* пишет api_request (+ дедуплицированный login). Весь блок в try/except — никогда не ломает HTTP-ответ.
  • app/main.py — регистрация middleware после RateLimitMiddleware.
  • app/api/v1/trade_in.pyestimate() получил request: Request; после успешного создания оценки пишет обогащённое estimate_request (адрес/площадь/комнаты + estimate_id для join) — «что искали».

Свойства

  • Не блокирует event-loop (threadpool), не задерживает ответ (лог после call_next), не ломает запрос (полный try/except + record_event никогда не raises).
  • Транзакция события декаплена от запроса — переживает rollback хендлера и наоборот.

Тесты

  • tests/test_user_events.py (21 passed, 1 skip — real-DB roundtrip самоскипается без Postgres), tests/test_request_audit.py (7 passed: api_request всегда, login по дедупу, public/no-header скип, exception в schedule не даёт 500).
  • Полный backend-suite как в CI: 2541 passed, 7 skipped. ruff чисто, CAST-trap grep пуст.

Данные, которые начнут копиться сразу после деплоя

login (кто/откуда/когда, deduped), api_request (активность по путям), estimate_request (что искали + адрес). Дальше: admin-API /admin/audit + /admin/analytics и дашборды.

## Что Серверный слой захвата событий для фич 2 (учёт входов/IP + что ищут) и базы фичи 3 (поведенческая аналитика). Пишет в `user_events` (миграция 184). ## Как - **`app/services/user_events.py`** (new): - `record_event(...)` — синхронный INSERT в свою `SessionLocal()` (decoupled от tx запроса), CAST(:ip AS inet)/uuid/jsonb, **никогда не raises** (сбой аудита не должен ронять запрос). - `schedule_event(...)` — fire-and-forget: в async-контексте уводит запись в threadpool (`asyncio.to_thread`) с done-callback (не блокирует event-loop, не теряет исключения); в sync-контексте (тесты) — inline. - `should_log_login(...)` — in-process best-effort дедуп: `login`-событие максимум раз в сутки на `(username, ip, user_agent)`. Рестарт сбрасывает set (недо-дедуп, не потеря; отчёты всё равно через DISTINCT). - **`app/core/request_audit.py`** (new) — `RequestAuditMiddleware`: после `call_next` для каждого аутентифицированного `/api/*` пишет `api_request` (+ дедуплицированный `login`). Весь блок в try/except — никогда не ломает HTTP-ответ. - **`app/main.py`** — регистрация middleware после `RateLimitMiddleware`. - **`app/api/v1/trade_in.py`** — `estimate()` получил `request: Request`; после успешного создания оценки пишет обогащённое `estimate_request` (адрес/площадь/комнаты + `estimate_id` для join) — «что искали». ## Свойства - Не блокирует event-loop (threadpool), не задерживает ответ (лог после `call_next`), не ломает запрос (полный try/except + record_event никогда не raises). - Транзакция события декаплена от запроса — переживает rollback хендлера и наоборот. ## Тесты - `tests/test_user_events.py` (21 passed, 1 skip — real-DB roundtrip самоскипается без Postgres), `tests/test_request_audit.py` (7 passed: api_request всегда, login по дедупу, public/no-header скип, exception в schedule не даёт 500). - Полный backend-suite как в CI: **2541 passed, 7 skipped**. ruff чисто, CAST-trap grep пуст. ## Данные, которые начнут копиться сразу после деплоя `login` (кто/откуда/когда, deduped), `api_request` (активность по путям), `estimate_request` (что искали + адрес). Дальше: admin-API `/admin/audit` + `/admin/analytics` и дашборды.
lekss361 added 1 commit 2026-07-13 20:07:55 +00:00
feat(tradein/audit): request-audit middleware + estimate_request logging → user_events
All checks were successful
CI Trade-In / backend-tests (pull_request) Successful in 59s
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
82928be326
Server-side capture layer for Feature 2 (login/IP audit) + base for Feature 3
(behavior analytics), on top of the already-deployed user_events table
(migration 184). record_event/schedule_event never raise into the caller —
audit logging must never break a real request.
lekss361 merged commit 32037af6de into main 2026-07-13 20:09:22 +00:00
lekss361 deleted branch feat/tradein-user-events-capture 2026-07-13 20:09:23 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2519
No description provided.