dashboard-nanobot/backend/services/platform_login_log_service.py

101 lines
3.4 KiB
Python

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,
)