101 lines
3.4 KiB
Python
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,
|
|
)
|