feat(5.6): observabilitate + jurnal aplicatie + lifecycle trimiteri blocate
Implementeaza PRD 5.6 complet (14 stories, TDD). Doua axe:
Lifecycle trimiteri blocate (Val A):
- submissions_admin.py: sterge/repune scoped (404 cross-account inaintea lui 409 stare)
- reactivare dedup peste `error` cu CAS (WHERE id=? AND status='error'), creds noi in
submissions + accounts.rar_creds_enc; worker invalideaza sesiunea RAR la creds proaspete
(JWT 30h vechi nu mai trimite cu parola gresita); camp aditiv `reactivated:true`
- retentie randuri blocate 30z; purge_expired exclude queued/sending; purge_after curatat
la reactivare/requeue
- API DELETE /v1/prezentari/{id} + /repune (200+JSON); UI butoane + bulk + banner actionabil
Observabilitate:
- app/observ.py log_event: dublu canal app_events (DB) + RotatingFileHandler per-proces,
redactare creds/PII la scriere (redact_pii/vin_partial)
- request_id middleware + X-Request-ID pe toate raspunsurile
- handler global excepții -> 500 envelope 6-chei + request_id (traceback doar in jurnal)
- audit cerere API (api_prezentari/api_auth_esuat) + audit worker (rar_login/tranzitii)
- tab "Jurnal" filtrabil scoped (non-admin doar contul sau); retentie jurnal 90z
- rar_error expus in GET /v1/prezentari/{id} (recovery observabil)
pytest -q: 741 passed, 0 failed. Docs: PRD raport VERIFY, contract endpointuri noi, ROADMAP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
66
app/db.py
66
app/db.py
@@ -143,3 +143,69 @@ def read_heartbeat(conn: sqlite3.Connection) -> sqlite3.Row | None:
|
||||
def queue_depth(conn: sqlite3.Connection) -> int:
|
||||
row = conn.execute("SELECT COUNT(*) AS n FROM submissions WHERE status='queued'").fetchone()
|
||||
return int(row["n"]) if row else 0
|
||||
|
||||
|
||||
# --- Jurnal de aplicatie (app_events, PRD 5.6 US-003) ---
|
||||
|
||||
def insert_app_event(
|
||||
conn: sqlite3.Connection,
|
||||
*,
|
||||
request_id: str | None,
|
||||
account_id: int | None,
|
||||
sursa: str,
|
||||
tip: str,
|
||||
nivel: str,
|
||||
cod: str | None,
|
||||
mesaj: str | None,
|
||||
context_json: str | None,
|
||||
purge_after: str | None,
|
||||
) -> None:
|
||||
"""Insert minimal intr-un rand app_events. Apelat DOAR prin observ.log_event
|
||||
(care a redactat deja toate valorile). Nu redacteaza aici — separarea de
|
||||
responsabilitati: db.py persista, observ.py/security.py curata."""
|
||||
conn.execute(
|
||||
"INSERT INTO app_events (request_id, account_id, sursa, tip, nivel, cod, mesaj, "
|
||||
"context_json, purge_after) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(request_id, account_id, sursa, tip, nivel, cod, mesaj, context_json, purge_after),
|
||||
)
|
||||
|
||||
|
||||
def read_app_events(
|
||||
conn: sqlite3.Connection,
|
||||
*,
|
||||
account_id: int | None = None,
|
||||
tip: str | None = None,
|
||||
nivel: str | None = None,
|
||||
date_from: str | None = None,
|
||||
date_to: str | None = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
) -> list[sqlite3.Row]:
|
||||
"""Citire paginata din app_events, ordine descrescatoare dupa id (cele mai noi intai).
|
||||
|
||||
account_id=None -> toate conturile (admin). account_id=int -> scoped pe cont
|
||||
(NULL apartine contului 1, ca restul UI-ului). Filtrele tip/nivel/data sunt optionale.
|
||||
"""
|
||||
where: list[str] = []
|
||||
params: list = []
|
||||
if account_id is not None:
|
||||
where.append("(account_id = ? OR (account_id IS NULL AND ? = 1))")
|
||||
params.extend([account_id, account_id])
|
||||
if tip:
|
||||
where.append("tip = ?")
|
||||
params.append(tip)
|
||||
if nivel:
|
||||
where.append("nivel = ?")
|
||||
params.append(nivel)
|
||||
if date_from:
|
||||
where.append("date(ts) >= date(?)")
|
||||
params.append(date_from)
|
||||
if date_to:
|
||||
where.append("date(ts) <= date(?)")
|
||||
params.append(date_to)
|
||||
sql = "SELECT id, ts, request_id, account_id, sursa, tip, nivel, cod, mesaj, context_json FROM app_events"
|
||||
if where:
|
||||
sql += " WHERE " + " AND ".join(where)
|
||||
sql += " ORDER BY id DESC LIMIT ? OFFSET ?"
|
||||
params.extend([limit, offset])
|
||||
return conn.execute(sql, params).fetchall()
|
||||
|
||||
Reference in New Issue
Block a user