import json from fastapi import Request from sqlalchemy.ext.asyncio import AsyncSession from app.models.audit_log import AuditLog def get_client_ip(request: Request) -> str: """Extract the real client IP from proxy headers, falling back to direct connection.""" forwarded_for = request.headers.get("x-forwarded-for") if forwarded_for: # X-Forwarded-For can be a comma-separated list; first entry is the original client return forwarded_for.split(",")[0].strip() real_ip = request.headers.get("x-real-ip") if real_ip: return real_ip.strip() return request.client.host if request.client else "unknown" async def log_audit_event( db: AsyncSession, action: str, actor_id: int | None = None, target_id: int | None = None, detail: dict | None = None, ip: str | None = None, ) -> None: """Record an action in the audit log. Does NOT commit — caller handles transaction.""" entry = AuditLog( actor_user_id=actor_id, target_user_id=target_id, action=action, detail=json.dumps(detail) if detail else None, ip_address=ip[:45] if ip else None, ) db.add(entry)