"""Админ: аутентификация (логин=ник + пароль) и управление сущностями.""" from __future__ import annotations from typing import Any from sqlmodel import Session, select from app.core.errors import InvalidCredentialsError, NotFoundError, ValidationError from app.core.security import verify_password from app.core.timeutil import iso_utc from app.models import AuditLog, Faction, Group, Match, MatchParticipant, User def authenticate_admin(session: Session, username: str, password: str) -> User: user = session.exec( select(User).where( User.nickname == username, User.role == "admin", User.auth_provider == "local", User.is_active == True, # noqa: E712 ) ).first() if user is None or not user.password_hash or not verify_password(password, user.password_hash): raise InvalidCredentialsError() return user # ─── Пользователи ──────────────────────────────────────────────────────────── def list_users(session: Session, query: str | None = None) -> list[User]: stmt = select(User).order_by(User.created_at.desc()) if query: stmt = stmt.where(User.nickname.contains(query)) return list(session.exec(stmt).all()) def update_user(session: Session, user_id: int, *, nickname: str | None = None, is_active: bool | None = None) -> User: user = session.get(User, user_id) if user is None: raise NotFoundError("Пользователь не найден.") if nickname is not None: user.nickname = nickname.strip() if is_active is not None: user.is_active = is_active session.add(user) session.commit() session.refresh(user) return user # Жёсткое удаление пользователя — dev-only, в services/admin_service нет намеренно: # логика вынесена в routers/dev_admin.py (файл исключён из прод/тест-образа). # ─── Группы ────────────────────────────────────────────────────────────────── def list_groups(session: Session) -> list[Group]: return list(session.exec(select(Group).order_by(Group.created_at.desc())).all()) def delete_group(session: Session, group_id: int) -> None: group = session.get(Group, group_id) if group is None: raise NotFoundError("Группа не найдена.") session.delete(group) session.commit() # ─── Партии ────────────────────────────────────────────────────────────────── def list_matches( session: Session, limit: int = 200, offset: int = 0, group_id: int | None = None, user_id: int | None = None, faction_id: int | None = None, ) -> list[tuple[Match, str]]: """Возвращает партии вместе с названием группы (для группировки в админке). Фильтры: по группе, по игроку-участнику, по фракции участника. """ stmt = ( select(Match, Group.name) .join(Group, Group.id == Match.group_id) .order_by(Match.group_id, Match.played_at.desc(), Match.id.desc()) ) if group_id is not None: stmt = stmt.where(Match.group_id == group_id) if user_id is not None: stmt = stmt.where( Match.id.in_( select(MatchParticipant.match_id).where(MatchParticipant.user_id == user_id) ) ) if faction_id is not None: stmt = stmt.where( Match.id.in_( select(MatchParticipant.match_id).where( MatchParticipant.faction_id == faction_id ) ) ) rows = session.exec(stmt.offset(offset).limit(limit)).all() return [(mt, gname) for mt, gname in rows] def rename_faction(session: Session, faction_id: int, name_ru: str) -> Faction: """Переименовывает фракцию во всей системе (имя хранится один раз).""" name_ru = (name_ru or "").strip() if not (1 <= len(name_ru) <= 64): raise ValidationError("Название фракции: 1–64 символа.") faction = session.get(Faction, faction_id) if faction is None: raise NotFoundError("Фракция не найдена.") faction.name_ru = name_ru session.add(faction) session.commit() session.refresh(faction) return faction def delete_match(session: Session, match_id: int) -> None: match = session.get(Match, match_id) if match is None: raise NotFoundError("Партия не найдена.") session.delete(match) session.commit() # ─── Журнал аудита ─────────────────────────────────────────────────────────── def list_audit_logs( session: Session, *, action: str | None = None, entity_type: str | None = None, limit: int = 100, offset: int = 0, ) -> dict[str, Any]: stmt = select(AuditLog).order_by(AuditLog.created_at.desc()) if action: stmt = stmt.where(AuditLog.action == action) if entity_type: stmt = stmt.where(AuditLog.entity_type == entity_type) rows = session.exec(stmt.offset(offset).limit(limit)).all() return { "items": [ { "id": r.id, "actor_id": r.actor_id, "action": r.action, "entity_type": r.entity_type, "entity_id": r.entity_id, "payload": r.payload, "ip": r.ip, "user_agent": r.user_agent, "created_at": iso_utc(r.created_at), } for r in rows ], "limit": limit, "offset": offset, }