v0.1 - макет интерфейса, аутентификация через логин, аккаунт админа, создание партии в 2 этапа, базовые настройки профиля и группы, переключение между группами, статистика
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
"""Админ: аутентификация (логин=ник + пароль) и управление сущностями."""
|
||||
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
|
||||
|
||||
|
||||
def delete_user(session: Session, user_id: int) -> None:
|
||||
user = session.get(User, user_id)
|
||||
if user is None:
|
||||
raise NotFoundError("Пользователь не найден.")
|
||||
session.delete(user)
|
||||
session.commit()
|
||||
|
||||
|
||||
# ─── Группы ──────────────────────────────────────────────────────────────────
|
||||
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user