"""Connection log API — query and stats.""" from datetime import datetime from fastapi import APIRouter, Depends, Query from sqlalchemy import select, func, desc, distinct from sqlalchemy.ext.asyncio import AsyncSession from ...database import get_db from ...models.log import ConnectionLog from ..auth import get_current_admin router = APIRouter(prefix="/logs", tags=["logs"]) @router.get("") async def get_logs( username: str | None = Query(None), channel: str | None = Query(None), content_type: str | None = Query(None), date_from: str | None = Query(None), date_to: str | None = Query(None), page: int = Query(1, ge=1), limit: int = Query(50, ge=1, le=200), db: AsyncSession = Depends(get_db), _=Depends(get_current_admin), ): q = select(ConnectionLog).order_by(desc(ConnectionLog.started_at)) if username: q = q.where(ConnectionLog.username.ilike(f"%{username}%")) if channel: q = q.where(ConnectionLog.channel_name.ilike(f"%{channel}%")) if content_type: q = q.where(ConnectionLog.content_type == content_type) if date_from: try: q = q.where(ConnectionLog.started_at >= datetime.fromisoformat(date_from)) except ValueError: pass if date_to: try: q = q.where(ConnectionLog.started_at <= datetime.fromisoformat(date_to)) except ValueError: pass total_result = await db.execute(select(func.count()).select_from(q.subquery())) total = total_result.scalar() or 0 q = q.offset((page - 1) * limit).limit(limit) result = await db.execute(q) logs = result.scalars().all() return { "total": total, "page": page, "limit": limit, "items": [_serialize(log) for log in logs], } @router.get("/stats") async def get_stats(db: AsyncSession = Depends(get_db), _=Depends(get_current_admin)): top_users_q = await db.execute( select( ConnectionLog.username, func.count().label("sessions"), func.sum(ConnectionLog.bytes_transferred).label("bytes"), ) .group_by(ConnectionLog.username) .order_by(desc("sessions")) .limit(10) ) top_channels_q = await db.execute( select(ConnectionLog.channel_name, func.count().label("sessions")) .where(ConnectionLog.channel_name.isnot(None)) .group_by(ConnectionLog.channel_name) .order_by(desc("sessions")) .limit(10) ) totals_q = await db.execute( select( func.count().label("total_sessions"), func.sum(ConnectionLog.bytes_transferred).label("total_bytes"), func.avg(ConnectionLog.duration_seconds).label("avg_duration"), func.count(distinct(ConnectionLog.username)).label("unique_users"), ) ) t = totals_q.one() return { "total_sessions": t.total_sessions or 0, "total_bytes": t.total_bytes or 0, "avg_duration_seconds": round(t.avg_duration or 0), "unique_users": t.unique_users or 0, "top_users": [ {"username": r.username, "sessions": r.sessions, "bytes": r.bytes or 0} for r in top_users_q ], "top_channels": [ {"channel_name": r.channel_name, "sessions": r.sessions} for r in top_channels_q ], } @router.delete("", status_code=204) async def clear_logs(db: AsyncSession = Depends(get_db), _=Depends(get_current_admin)): """Delete all logs (admin use).""" from sqlalchemy import delete await db.execute(delete(ConnectionLog)) await db.commit() def _serialize(log: ConnectionLog) -> dict: return { "id": log.id, "user_id": log.user_id, "username": log.username, "channel_id": log.channel_id, "channel_name": log.channel_name, "content_type": log.content_type, "stream_id": log.stream_id, "started_at": log.started_at.isoformat() if log.started_at else None, "ended_at": log.ended_at.isoformat() if log.ended_at else None, "duration_seconds": log.duration_seconds, "bytes_transferred": log.bytes_transferred, "client_ip": log.client_ip, }