/
Mihaham
/
Table-Time
Обзор
Документация
Войти
/
Mihaham
/
Table-Time
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
services/api/app/modules/admin/security_service.py
236 строк
9 KB
MihahamYT
feat: dedicated web panels for all 46 games
14 июн 2026, 21:36
14 июн 2026, 21:36
4e11c3f
Код
Авторство
О чём код?
import uuid from datetime import datetime from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.db.models import GameAction, GameEvent, GameInstance, Participant, SecurityEvent, Session class SecurityService: def __init__(self, db: AsyncSession): self.db = db async def list_events( self, *, page: int = 1, limit: int = 50, category: str | None = None, severity: str | None = None, from_date: datetime | None = None, to_date: datetime | None = None, ) -> dict: query = select(SecurityEvent).order_by(SecurityEvent.created_at.desc()) if category: query = query.where(SecurityEvent.category == category) if severity: query = query.where(SecurityEvent.severity == severity) if from_date: query = query.where(SecurityEvent.created_at >= from_date) if to_date: query = query.where(SecurityEvent.created_at <= to_date) count_query = select(func.count()).select_from(query.subquery()) total = (await self.db.execute(count_query)).scalar() or 0 offset = (page - 1) * limit result = await self.db.execute(query.offset(offset).limit(limit)) events = result.scalars().all() return { "events": [self._event_dict(e) for e in events], "total": total, "page": page, "limit": limit, } async def list_game_rejections( self, *, page: int = 1, limit: int = 50, plugin_id: str | None = None, error_code: str | None = None, from_date: datetime | None = None, to_date: datetime | None = None, ) -> dict: query = ( select(GameEvent, GameInstance, Session) .outerjoin(GameInstance, GameEvent.game_instance_id == GameInstance.id) .join(Session, GameEvent.session_id == Session.id) .where(GameEvent.event_type == "action.rejected") .order_by(GameEvent.created_at.desc()) ) if plugin_id: query = query.where(GameInstance.plugin_id == plugin_id) if from_date: query = query.where(GameEvent.created_at >= from_date) if to_date: query = query.where(GameEvent.created_at <= to_date) count_query = select(func.count()).select_from(query.subquery()) total = (await self.db.execute(count_query)).scalar() or 0 offset = (page - 1) * limit result = await self.db.execute(query.offset(offset).limit(limit)) rows = result.all() player_ids = {row[0].player_id for row in rows if row[0].player_id} names: dict[str, str] = {} if player_ids: p_result = await self.db.execute(select(Participant).where(Participant.id.in_(player_ids))) for p in p_result.scalars().all(): names[str(p.id)] = p.display_name rejections = [] for event, game, session in rows: payload = event.payload or {} code = payload.get("error_code", "UNKNOWN") if error_code and code != error_code: continue rejections.append({ "id": str(event.id), "game_id": str(event.game_instance_id) if event.game_instance_id else None, "session_id": str(event.session_id), "plugin_id": game.plugin_id if game else None, "invite_code": session.invite_code, "player_id": str(event.player_id) if event.player_id else None, "player_name": names.get(str(event.player_id)) if event.player_id else None, "error_code": code, "action_type": payload.get("action_type", ""), "channel": payload.get("channel"), "created_at": event.created_at.isoformat(), }) return { "rejections": rejections, "total": total if not error_code else len(rejections), "page": page, "limit": limit, } async def security_summary_24h(self) -> dict: from datetime import UTC, datetime, timedelta start = datetime.now(UTC) - timedelta(hours=24) result = await self.db.execute( select(SecurityEvent.category, func.count()) .where(SecurityEvent.created_at >= start) .group_by(SecurityEvent.category) ) by_category = {row[0]: row[1] for row in result.all()} return { "failed_logins": by_category.get("auth_failed", 0), "admin_otp_failed": by_category.get("admin_otp_failed", 0), "rate_limited": by_category.get("rate_limited", 0), "invalid_token": by_category.get("invalid_token", 0), } def _event_dict(self, event: SecurityEvent) -> dict: return { "id": str(event.id), "category": event.category, "severity": event.severity, "source_ip": event.source_ip, "user_agent": event.user_agent, "actor_id": event.actor_id, "resource": event.resource, "detail": event.detail, "created_at": event.created_at.isoformat(), } async def get_session_detail(self, session_id: uuid.UUID) -> dict | None: result = await self.db.execute( select(Session) .options(selectinload(Session.participants), selectinload(Session.game_instances)) .where(Session.id == session_id) ) session = result.scalar_one_or_none() if not session: return None events_result = await self.db.execute( select(GameEvent) .where(GameEvent.session_id == session_id, GameEvent.game_instance_id.is_(None)) .order_by(GameEvent.created_at) ) session_events = events_result.scalars().all() names = {str(p.id): p.display_name for p in session.participants} games = sorted(session.game_instances, key=lambda g: g.started_at, reverse=True) winner_ids = {g.winner_id for g in games if g.winner_id} winner_names: dict[str, str] = {} for p in session.participants: if p.id in winner_ids: winner_names[str(p.id)] = p.display_name game_ids = [g.id for g in games] action_counts: dict[uuid.UUID, int] = {} rejection_counts: dict[uuid.UUID, int] = {} if game_ids: ac = await self.db.execute( select(GameAction.game_instance_id, func.count()) .where(GameAction.game_instance_id.in_(game_ids)) .group_by(GameAction.game_instance_id) ) action_counts = {row[0]: row[1] for row in ac.all()} rc = await self.db.execute( select(GameEvent.game_instance_id, func.count()) .where( GameEvent.game_instance_id.in_(game_ids), GameEvent.event_type == "action.rejected", ) .group_by(GameEvent.game_instance_id) ) rejection_counts = {row[0]: row[1] for row in rc.all()} return { "session": { "id": str(session.id), "invite_code": session.invite_code, "status": session.status, "created_at": session.created_at.isoformat(), "closed_at": session.closed_at.isoformat() if session.closed_at else None, "game_count": len(games), "participant_count": len(session.participants), "host_participant_id": str(session.host_participant_id) if session.host_participant_id else None, }, "participants": [ { "id": str(p.id), "display_name": p.display_name, "role": p.role, "control_channel": p.control_channel, "presence_web": p.presence_web, "presence_telegram": p.presence_telegram, } for p in session.participants ], "games": [ { "id": str(g.id), "plugin_id": g.plugin_id, "session_id": str(session.id), "invite_code": session.invite_code, "status": g.status, "started_at": g.started_at.isoformat(), "finished_at": g.finished_at.isoformat() if g.finished_at else None, "winner_id": str(g.winner_id) if g.winner_id else None, "winner_name": winner_names.get(str(g.winner_id)) if g.winner_id else None, "action_count": action_counts.get(g.id, 0), "rejection_count": rejection_counts.get(g.id, 0), } for g in games ], "events": [ { "event_type": e.event_type, "payload": e.payload, "created_at": e.created_at.isoformat(), "player_name": names.get(str(e.player_id)) if e.player_id else None, } for e in session_events ], }