Files
ForbiddenStarsApp/backend/app/routers/matches.py
T

216 lines
7.7 KiB
Python

"""Роутер партий: рандом фракции, старт, завершение, детали, правка, удаление."""
from __future__ import annotations
from fastapi import APIRouter, Depends, Request
from sqlmodel import Session
from app.auth.deps import get_current_user
from app.core.errors import NoGroupError
from app.core.timeutil import iso_utc
from app.db.session import get_session
from app.models import Match, User
from app.schemas import api as s
from app.services import audit_service, group_service, match_service
from app.services.match_service import FinishInput, ParticipantInput, RosterInput
router = APIRouter(prefix="/matches", tags=["matches"])
def _ensure_has_any_group(session: Session, user: User) -> None:
if not group_service.list_user_groups(session, user.id): # type: ignore[arg-type]
raise NoGroupError()
def build_match_read(session: Session, match: Match, *, can_modify: bool = False) -> s.MatchRead:
parts = [
s.MatchParticipantRead(
user_id=u.id, # type: ignore[arg-type]
nickname=u.nickname,
faction_id=f.id, # type: ignore[arg-type]
faction_name=f.name_ru,
place=p.place,
was_random=p.was_random,
comment=p.comment,
)
for p, u, f in match_service.participants_detail(session, match.id) # type: ignore[arg-type]
]
return s.MatchRead(
id=match.id, # type: ignore[arg-type]
group_id=match.group_id,
status=match.status,
played_at=match.played_at,
started_at=iso_utc(match.started_at),
finished_at=iso_utc(match.finished_at),
duration_minutes=match.duration_minutes,
win_reason=match.win_reason, # type: ignore[arg-type]
player_count=match.player_count,
overall_comment=match.overall_comment,
created_by=match.created_by,
can_modify=can_modify,
participants=parts,
attachments=[],
)
@router.post("/randomize-faction", response_model=s.RandomizeResponse)
def randomize_faction(
body: s.RandomizeRequest,
session: Session = Depends(get_session),
user: User = Depends(get_current_user),
) -> s.RandomizeResponse:
_ensure_has_any_group(session, user)
group_service.assert_member(session, body.group_id, user.id) # type: ignore[arg-type]
faction = match_service.randomize_faction(session, body.group_id, body.exclude_faction_ids)
return s.RandomizeResponse(
faction=s.FactionRead(
id=faction.id, code=faction.code, name_ru=faction.name_ru, expansion_id=faction.expansion_id # type: ignore[arg-type]
)
)
@router.post("", response_model=s.MatchRead)
def start_match(
body: s.MatchCreate,
request: Request,
session: Session = Depends(get_session),
user: User = Depends(get_current_user),
) -> s.MatchRead:
"""Этап 1: старт партии (выбор игроков и фракций)."""
_ensure_has_any_group(session, user)
roster = [
RosterInput(user_id=p.user_id, faction_id=p.faction_id, was_random=p.was_random)
for p in body.participants
]
match = match_service.create_match(session, user, group_id=body.group_id, roster=roster)
audit_service.record(
session,
actor_id=user.id,
action="create",
entity_type="match",
entity_id=match.id,
payload={"group_id": match.group_id, "player_count": match.player_count, "status": "in_progress"},
ip=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
session.commit()
return build_match_read(session, match, can_modify=match_service.can_modify(session, match, user))
@router.post("/{match_id}/finish", response_model=s.MatchRead)
def finish_match(
match_id: int,
body: s.MatchFinish,
request: Request,
session: Session = Depends(get_session),
user: User = Depends(get_current_user),
) -> s.MatchRead:
"""Этап 2: завершение партии (места, причина победы, длительность)."""
match = match_service.get_match(session, match_id)
match_service.assert_can_modify(session, match, user)
finish = [
FinishInput(user_id=p.user_id, place=p.place, comment=p.comment, faction_id=p.faction_id)
for p in body.participants
]
match = match_service.finish_match(
session,
match,
finish=finish,
win_reason=body.win_reason,
overall_comment=body.overall_comment,
overall_comment_set=("overall_comment" in body.model_fields_set),
)
audit_service.record(
session,
actor_id=user.id,
action="update",
entity_type="match",
entity_id=match.id,
payload={"event": "finish", "win_reason": match.win_reason, "duration_minutes": match.duration_minutes},
ip=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
session.commit()
return build_match_read(session, match, can_modify=match_service.can_modify(session, match, user))
@router.get("/{match_id}", response_model=s.MatchRead)
def get_match(
match_id: int,
session: Session = Depends(get_session),
user: User = Depends(get_current_user),
) -> s.MatchRead:
match = match_service.get_match(session, match_id)
group_service.assert_member(session, match.group_id, user.id) # type: ignore[arg-type]
return build_match_read(session, match, can_modify=match_service.can_modify(session, match, user))
@router.patch("/{match_id}", response_model=s.MatchRead)
def update_match(
match_id: int,
body: s.MatchUpdate,
request: Request,
session: Session = Depends(get_session),
user: User = Depends(get_current_user),
) -> s.MatchRead:
match = match_service.get_match(session, match_id)
match_service.assert_can_modify(session, match, user)
participants = None
if body.participants is not None:
participants = [
ParticipantInput(
user_id=p.user_id,
faction_id=p.faction_id,
place=p.place,
was_random=p.was_random,
comment=p.comment,
)
for p in body.participants
]
match = match_service.update_match(
session,
match,
played_at=body.played_at,
overall_comment=body.overall_comment,
overall_comment_set=("overall_comment" in body.model_fields_set),
win_reason=body.win_reason,
win_reason_set=("win_reason" in body.model_fields_set),
participants=participants,
)
audit_service.record(
session,
actor_id=user.id,
action="update",
entity_type="match",
entity_id=match.id,
ip=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
session.commit()
return build_match_read(session, match, can_modify=match_service.can_modify(session, match, user))
@router.delete("/{match_id}", response_model=s.OkResponse)
def delete_match(
match_id: int,
request: Request,
session: Session = Depends(get_session),
user: User = Depends(get_current_user),
) -> s.OkResponse:
match = match_service.get_match(session, match_id)
match_service.assert_can_modify(session, match, user)
match_id_val = match.id
group_id_val = match.group_id
match_service.delete_match(session, match)
audit_service.record(
session,
actor_id=user.id,
action="delete",
entity_type="match",
entity_id=match_id_val,
payload={"group_id": group_id_val},
ip=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
session.commit()
return s.OkResponse()