165 lines
6.2 KiB
Python
165 lines
6.2 KiB
Python
"""Админ: аутентификация (логин=ник + пароль) и управление сущностями."""
|
|
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,
|
|
}
|