- db-backup sidecar: pg_dump -Fc of procurement+casdoor every BACKUP_INTERVAL_SECONDS into BACKUP_DIR, retention via BACKUP_KEEP_DAYS - audit_log table (migration.sql + runtime ensure_audit_table) with model + transactional helper (utils/audit.record) — wired into user create/role-change and inventory transaction creation - get_actor: quote_events.actor now stores the VERIFIED user's display name instead of the raw bearer token
42 lines
1.2 KiB
Python
42 lines
1.2 KiB
Python
"""Audit helper — one row per sensitive mutation, written in the SAME
|
|
transaction as the change (rolled back together with it).
|
|
|
|
await record(db, actor=ctx.display_name, action="user.role_changed",
|
|
entity="user", entity_id=str(user.id),
|
|
details={"from": old_role, "to": user.role})
|
|
|
|
Never raises: an audit failure must not fail the business operation.
|
|
"""
|
|
import logging
|
|
from typing import Any
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.audit import AuditLog
|
|
|
|
logger = logging.getLogger("procurement.audit")
|
|
|
|
|
|
async def record(
|
|
db: AsyncSession,
|
|
*,
|
|
actor: str | None,
|
|
action: str,
|
|
entity: str,
|
|
entity_id: str | None = None,
|
|
details: dict[str, Any] | None = None,
|
|
) -> None:
|
|
try:
|
|
db.add(
|
|
AuditLog(
|
|
actor=actor,
|
|
action=action,
|
|
entity=entity,
|
|
entity_id=str(entity_id) if entity_id else None,
|
|
details=details or {},
|
|
)
|
|
)
|
|
await db.flush()
|
|
except Exception: # noqa: BLE001 — audit must never break the caller
|
|
logger.exception("failed to write audit_log row (%s/%s)", action, entity)
|