This commit is contained in:
Sergey Antropoff
2026-07-17 15:57:36 +03:00
commit e4240a7be5
72 changed files with 6227 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from typing import Any
from sqlalchemy import delete, func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models import AuditEvent
KNOWN_AUDIT_EVENT_TYPES = [
"wrap.create",
"wrap.unwrap",
"admin.login",
"admin.logout",
"admin.settings_update",
"admin.purge",
]
async def write_audit(
db: AsyncSession,
*,
event_type: str,
success: bool,
wrap_id: str | None = None,
ip: str | None = None,
user_agent: str | None = None,
accept_language: str | None = None,
forwarded_for: str | None = None,
details: dict[str, Any] | None = None,
) -> None:
event = AuditEvent(
event_type=event_type,
success=success,
wrap_id=wrap_id,
ip=ip,
user_agent=(user_agent or "")[:512] or None,
accept_language=(accept_language or "")[:128] or None,
forwarded_for=(forwarded_for or "")[:256] or None,
details=details or {},
)
db.add(event)
await db.commit()
async def purge_old_audit(db: AsyncSession, retention_days: int) -> int:
cutoff = datetime.now(timezone.utc) - timedelta(days=retention_days)
result = await db.execute(delete(AuditEvent).where(AuditEvent.created_at < cutoff))
await db.commit()
return result.rowcount or 0
def _audit_filters(event_type: str | None, wrap_id: str | None):
filters = []
if event_type:
filters.append(AuditEvent.event_type == event_type)
if wrap_id:
filters.append(AuditEvent.wrap_id == wrap_id)
return filters
async def list_audit(
db: AsyncSession,
*,
limit: int = 100,
offset: int = 0,
event_type: str | None = None,
wrap_id: str | None = None,
) -> list[AuditEvent]:
stmt = select(AuditEvent).order_by(AuditEvent.created_at.desc()).offset(offset).limit(limit)
for f in _audit_filters(event_type, wrap_id):
stmt = stmt.where(f)
result = await db.execute(stmt)
return list(result.scalars().all())
async def count_audit(
db: AsyncSession,
*,
event_type: str | None = None,
wrap_id: str | None = None,
) -> int:
stmt = select(func.count()).select_from(AuditEvent)
for f in _audit_filters(event_type, wrap_id):
stmt = stmt.where(f)
result = await db.execute(stmt)
return int(result.scalar_one() or 0)
async def list_audit_event_types(db: AsyncSession) -> list[str]:
"""Known types plus distinct values already stored in DB (dynamic dropdown)."""
result = await db.execute(
select(AuditEvent.event_type).distinct().order_by(AuditEvent.event_type)
)
from_db = [row[0] for row in result.all() if row[0]]
return sorted(set(KNOWN_AUDIT_EVENT_TYPES) | set(from_db))