from __future__ import annotations from datetime import datetime from typing import Optional from sqlalchemy import func, or_ from sqlmodel import Session, select from models.auth import AuthLoginLog from schemas.platform import PlatformLoginLogItem, PlatformLoginLogResponse def _to_iso(value: Optional[datetime]) -> Optional[str]: if value is None: return None return value.isoformat() + "Z" def _normalize_status(value: str) -> str: normalized = str(value or "").strip().lower() if normalized in {"active", "revoked"}: return normalized return "all" def list_login_logs( session: Session, *, search: str = "", auth_type: str = "", status: str = "all", limit: int = 50, offset: int = 0, ) -> PlatformLoginLogResponse: normalized_search = str(search or "").strip() normalized_type = str(auth_type or "").strip().lower() normalized_status = _normalize_status(status) normalized_limit = max(1, min(200, int(limit or 50))) normalized_offset = max(0, int(offset or 0)) stmt = select(AuthLoginLog) count_stmt = select(func.count()).select_from(AuthLoginLog) if normalized_type in {"panel", "bot"}: stmt = stmt.where(AuthLoginLog.auth_type == normalized_type) count_stmt = count_stmt.where(AuthLoginLog.auth_type == normalized_type) if normalized_status == "active": stmt = stmt.where(AuthLoginLog.revoked_at == None) # noqa: E711 count_stmt = count_stmt.where(AuthLoginLog.revoked_at == None) # noqa: E711 elif normalized_status == "revoked": stmt = stmt.where(AuthLoginLog.revoked_at != None) # noqa: E711 count_stmt = count_stmt.where(AuthLoginLog.revoked_at != None) # noqa: E711 if normalized_search: like_value = f"%{normalized_search}%" search_filter = or_( AuthLoginLog.subject_id.ilike(like_value), AuthLoginLog.bot_id.ilike(like_value), AuthLoginLog.client_ip.ilike(like_value), AuthLoginLog.device_info.ilike(like_value), AuthLoginLog.user_agent.ilike(like_value), AuthLoginLog.auth_source.ilike(like_value), AuthLoginLog.revoke_reason.ilike(like_value), ) stmt = stmt.where(search_filter) count_stmt = count_stmt.where(search_filter) total = int(session.exec(count_stmt).one() or 0) rows = session.exec( stmt.order_by(AuthLoginLog.created_at.desc(), AuthLoginLog.id.desc()).offset(normalized_offset).limit(normalized_limit) ).all() items = [ PlatformLoginLogItem( id=int(row.id or 0), auth_type=row.auth_type, subject_id=row.subject_id, bot_id=row.bot_id, auth_source=str(row.auth_source or ""), client_ip=row.client_ip, user_agent=row.user_agent, device_info=row.device_info, created_at=_to_iso(row.created_at) or "", last_seen_at=_to_iso(row.last_seen_at), expires_at=_to_iso(row.expires_at), revoked_at=_to_iso(row.revoked_at), revoke_reason=row.revoke_reason, status="revoked" if row.revoked_at else "active", ) for row in rows ] return PlatformLoginLogResponse( items=items, total=total, limit=normalized_limit, offset=normalized_offset, has_more=normalized_offset + len(items) < total, )