gendesign/tradein-mvp/backend/app/schemas/audit.py
lekss361 9fd6396fbc
All checks were successful
Deploy Trade-In / build-backend (push) Successful in 57s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 30s
Deploy Trade-In / test (push) Successful in 4m49s
Deploy Trade-In / deploy (push) Successful in 50s
Deploy Trade-In / changes (push) Successful in 10s
feat(tradein/admin): audit + analytics read API over user_events (#2520)
Admin-only read API: GET /admin/audit/accounts, /admin/audit/accounts/{username}, /admin/analytics over user_events. Read-only, empty-safe, RBAC via central gate.
2026-07-13 20:26:58 +00:00

146 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Pydantic-схемы admin read API над `user_events` (Feature 2 audit + Feature 3 analytics).
`user_events` (migration `184_user_events.sql`) — unified append-only событийный лог
(login/IP audit + behavior analytics), admin-read-only. Эти схемы описывают ответы
`GET /api/v1/admin/audit/*` и `GET /api/v1/admin/analytics` (см. app/api/v1/audit.py).
"""
from __future__ import annotations
from datetime import date, datetime
from pydantic import BaseModel, ConfigDict
class AccountSummary(BaseModel):
"""Одна строка списка `GET /audit/accounts` — сводка по одному username."""
model_config = ConfigDict(from_attributes=True)
username: str
first_seen_at: datetime
last_seen_at: datetime
distinct_ips: int
distinct_devices: int
login_count: int
request_count: int
search_count: int
class AccountIpEntry(BaseModel):
"""Одна строка `ips` в drilldown `GET /audit/accounts/{username}`."""
model_config = ConfigDict(from_attributes=True)
ip_address: str | None = None
event_count: int
first_seen: datetime
last_seen: datetime
class AccountDeviceEntry(BaseModel):
"""Одна строка `devices` в drilldown `GET /audit/accounts/{username}`."""
model_config = ConfigDict(from_attributes=True)
user_agent: str | None = None
event_count: int
first_seen: datetime
last_seen: datetime
class AccountSearchEntry(BaseModel):
"""Одна строка `searches` в drilldown — недавний estimate_request."""
model_config = ConfigDict(from_attributes=True)
address: str | None = None
area_m2: str | None = None
rooms: str | None = None
estimate_id: str | None = None
ip_address: str | None = None
created_at: datetime
class AccountActivityEntry(BaseModel):
"""Одна строка `recent_activity` в drilldown — сырое событие."""
model_config = ConfigDict(from_attributes=True)
event_type: str
path: str | None = None
method: str | None = None
ip_address: str | None = None
created_at: datetime
class AccountDrilldown(BaseModel):
"""Полный ответ `GET /audit/accounts/{username}` — 4 списка.
Неизвестный username НЕ 404 — это просто пустой отчёт (все списки == []).
"""
ips: list[AccountIpEntry]
devices: list[AccountDeviceEntry]
searches: list[AccountSearchEntry]
recent_activity: list[AccountActivityEntry]
class AnalyticsSummary(BaseModel):
"""Верхнеуровневые счётчики дашборда `GET /analytics`."""
model_config = ConfigDict(from_attributes=True)
total_events: int
distinct_users: int
events_last_24h: int
active_users_last_24h: int
class AnalyticsDailyPoint(BaseModel):
"""Одна точка time-series `daily` — события/пользователи за день."""
model_config = ConfigDict(from_attributes=True)
day: date
events: int
users: int
class AnalyticsTopSearch(BaseModel):
"""Одна строка `top_searches` — самый частый искомый адрес."""
model_config = ConfigDict(from_attributes=True)
address: str | None = None
n: int
class AnalyticsTopPath(BaseModel):
"""Одна строка `top_paths` — самый частый API-путь."""
model_config = ConfigDict(from_attributes=True)
path: str | None = None
n: int
class AnalyticsByAccount(BaseModel):
"""Одна строка `by_account` — сводка активности по username."""
model_config = ConfigDict(from_attributes=True)
username: str
events: int
searches: int
last_seen: datetime
class AnalyticsDashboard(BaseModel):
"""Полный ответ `GET /analytics` — бандл для дашборда Feature 3."""
summary: AnalyticsSummary
daily: list[AnalyticsDailyPoint]
top_searches: list[AnalyticsTopSearch]
top_paths: list[AnalyticsTopPath]
by_account: list[AnalyticsByAccount]