"""Админ-роутер: отдельный вход (логин+пароль) и управление сущностями.""" from __future__ import annotations from fastapi import APIRouter, Depends, Query, Request, Response from sqlmodel import Session from app.auth.deps import get_current_admin from app.core import security from app.core.timeutil import iso_utc from app.db.session import get_session from app.models import User from app.routers.matches import build_match_read from app.schemas import api as s from app.services import admin_service, audit_service, faction_service, match_service from app.services.match_service import ParticipantInput router = APIRouter(prefix="/admin", tags=["admin"]) # ─── Аутентификация админа ─────────────────────────────────────────────────── @router.post("/auth/login", response_model=s.AdminMe) def admin_login( body: s.AdminLogin, request: Request, response: Response, session: Session = Depends(get_session), ) -> s.AdminMe: admin = admin_service.authenticate_admin(session, body.username, body.password) security.set_admin_session(response, admin.id) # type: ignore[arg-type] audit_service.record( session, actor_id=admin.id, action="login", entity_type="admin", entity_id=admin.id, ip=request.client.host if request.client else None, user_agent=request.headers.get("user-agent"), ) session.commit() return s.AdminMe(id=admin.id, nickname=admin.nickname, role=admin.role) # type: ignore[arg-type] @router.post("/auth/logout", response_model=s.OkResponse) def admin_logout(response: Response) -> s.OkResponse: security.clear_admin_session(response) return s.OkResponse() @router.get("/me", response_model=s.AdminMe) def admin_me(admin: User = Depends(get_current_admin)) -> s.AdminMe: return s.AdminMe(id=admin.id, nickname=admin.nickname, role=admin.role) # type: ignore[arg-type] # ─── Пользователи ──────────────────────────────────────────────────────────── @router.get("/users", response_model=list[s.AdminUserRead]) def list_users( query: str | None = Query(None), session: Session = Depends(get_session), _admin: User = Depends(get_current_admin), ) -> list[s.AdminUserRead]: return [ s.AdminUserRead( id=u.id, # type: ignore[arg-type] nickname=u.nickname, role=u.role, is_active=u.is_active, auth_provider=u.auth_provider, telegram_id=u.telegram_id, created_at=iso_utc(u.created_at), ) for u in admin_service.list_users(session, query) ] @router.patch("/users/{user_id}", response_model=s.AdminUserRead) def update_user( user_id: int, body: s.AdminUserUpdate, request: Request, session: Session = Depends(get_session), admin: User = Depends(get_current_admin), ) -> s.AdminUserRead: u = admin_service.update_user(session, user_id, nickname=body.nickname, is_active=body.is_active) audit_service.record( session, actor_id=admin.id, action="update", entity_type="user", entity_id=user_id, payload=body.model_dump(exclude_none=True), ip=request.client.host if request.client else None, ) session.commit() return s.AdminUserRead( id=u.id, # type: ignore[arg-type] nickname=u.nickname, role=u.role, is_active=u.is_active, auth_provider=u.auth_provider, telegram_id=u.telegram_id, created_at=u.created_at.isoformat(), ) @router.delete("/users/{user_id}", response_model=s.OkResponse) def delete_user( user_id: int, request: Request, session: Session = Depends(get_session), admin: User = Depends(get_current_admin), ) -> s.OkResponse: admin_service.delete_user(session, user_id) audit_service.record( session, actor_id=admin.id, action="delete", entity_type="user", entity_id=user_id, ip=request.client.host if request.client else None, ) session.commit() return s.OkResponse() # ─── Группы ────────────────────────────────────────────────────────────────── @router.get("/groups", response_model=list[s.AdminGroupRead]) def list_groups( session: Session = Depends(get_session), _admin: User = Depends(get_current_admin), ) -> list[s.AdminGroupRead]: return [ s.AdminGroupRead( id=g.id, name=g.name, owner_id=g.owner_id, created_at=iso_utc(g.created_at) # type: ignore[arg-type] ) for g in admin_service.list_groups(session) ] @router.delete("/groups/{group_id}", response_model=s.OkResponse) def delete_group( group_id: int, request: Request, session: Session = Depends(get_session), admin: User = Depends(get_current_admin), ) -> s.OkResponse: admin_service.delete_group(session, group_id) audit_service.record( session, actor_id=admin.id, action="delete", entity_type="group", entity_id=group_id, ip=request.client.host if request.client else None, ) session.commit() return s.OkResponse() # ─── Партии ────────────────────────────────────────────────────────────────── @router.get("/matches", response_model=list[s.AdminMatchRead]) def list_matches( group_id: int | None = Query(None), user_id: int | None = Query(None), faction_id: int | None = Query(None), limit: int = Query(200, ge=1, le=500), offset: int = Query(0, ge=0), session: Session = Depends(get_session), _admin: User = Depends(get_current_admin), ) -> list[s.AdminMatchRead]: return [ s.AdminMatchRead( id=m.id, # type: ignore[arg-type] group_id=m.group_id, group_name=gname, status=m.status, played_at=str(m.played_at), duration_minutes=m.duration_minutes, win_reason=m.win_reason, # type: ignore[arg-type] player_count=m.player_count, created_by=m.created_by, created_at=iso_utc(m.created_at), ) for m, gname in admin_service.list_matches( session, limit=limit, offset=offset, group_id=group_id, user_id=user_id, faction_id=faction_id, ) ] @router.get("/matches/{match_id}", response_model=s.MatchRead) def get_match( match_id: int, session: Session = Depends(get_session), _admin: User = Depends(get_current_admin), ) -> s.MatchRead: match = match_service.get_match(session, match_id) return build_match_read(session, match, can_modify=True) @router.patch("/matches/{match_id}", response_model=s.MatchRead) def update_match( match_id: int, body: s.MatchUpdate, request: Request, session: Session = Depends(get_session), admin: User = Depends(get_current_admin), ) -> s.MatchRead: match = match_service.get_match(session, match_id) 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=admin.id, action="update", entity_type="match", entity_id=match.id, ip=request.client.host if request.client else None, ) session.commit() return build_match_read(session, match, can_modify=True) # ─── Фракции (переименование во всей системе) ───────────────────────────────── @router.get("/factions", response_model=list[s.FactionRead]) def list_factions( session: Session = Depends(get_session), _admin: User = Depends(get_current_admin), ) -> list[s.FactionRead]: return [ s.FactionRead( id=f.id, code=f.code, name_ru=f.name_ru, expansion_id=f.expansion_id # type: ignore[arg-type] ) for f in faction_service.list_factions(session) ] @router.patch("/factions/{faction_id}", response_model=s.FactionRead) def rename_faction( faction_id: int, body: s.FactionRename, request: Request, session: Session = Depends(get_session), admin: User = Depends(get_current_admin), ) -> s.FactionRead: f = admin_service.rename_faction(session, faction_id, body.name_ru) audit_service.record( session, actor_id=admin.id, action="update", entity_type="faction", entity_id=faction_id, payload={"name_ru": f.name_ru}, ip=request.client.host if request.client else None, ) session.commit() return s.FactionRead( id=f.id, code=f.code, name_ru=f.name_ru, expansion_id=f.expansion_id # type: ignore[arg-type] ) @router.delete("/matches/{match_id}", response_model=s.OkResponse) def delete_match( match_id: int, request: Request, session: Session = Depends(get_session), admin: User = Depends(get_current_admin), ) -> s.OkResponse: admin_service.delete_match(session, match_id) audit_service.record( session, actor_id=admin.id, action="delete", entity_type="match", entity_id=match_id, ip=request.client.host if request.client else None, ) session.commit() return s.OkResponse() # ─── Журнал аудита ─────────────────────────────────────────────────────────── @router.get("/audit-logs", response_model=s.AuditLogList) def audit_logs( action: str | None = Query(None), entity_type: str | None = Query(None), limit: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0), session: Session = Depends(get_session), _admin: User = Depends(get_current_admin), ) -> dict: return admin_service.list_audit_logs( session, action=action, entity_type=entity_type, limit=limit, offset=offset )