"""Раздел «Звания»: справочник магазина званий.""" from __future__ import annotations from fastapi import APIRouter, HTTPException, Request, Response from fastapi.responses import HTMLResponse, RedirectResponse from admin.security import check_csrf from common import __version__ from common.repositories import titles_repo router = APIRouter(prefix="/titles", tags=["titles"]) MAX_TITLE_LENGTH = 100 def _parse(title: str, price: str, visible: bool) -> tuple[dict[str, object] | None, list[str]]: errors: list[str] = [] title = title.strip() if not title: errors.append("Название не может быть пустым") elif len(title) > MAX_TITLE_LENGTH: errors.append(f"Название длиннее {MAX_TITLE_LENGTH} символов") try: price_value = int(price) except (TypeError, ValueError): price_value = -1 errors.append("Цена должна быть целым числом") if price_value < 0 and "Цена должна быть целым числом" not in errors: errors.append("Цена не может быть отрицательной") if errors: return None, errors return {"title": title, "price": price_value, "visible": visible}, [] async def _render( request: Request, *, errors: list[str] | None = None, form: dict[str, str] | None = None, saved: str | None = None, status_code: int = 200, ) -> HTMLResponse: return request.app.state.templates.TemplateResponse( request=request, name="titles/list.html", context={ "version": __version__, "titles": await titles_repo.list_titles(), "errors": errors or [], "form": form or {"title": "", "price": ""}, "saved": saved, "max_title_length": MAX_TITLE_LENGTH, }, status_code=status_code, ) @router.get("", response_class=HTMLResponse) async def list_titles(request: Request, saved: str | None = None) -> HTMLResponse: return await _render(request, saved=saved) @router.post("") async def create_title(request: Request) -> Response: form = await request.form() check_csrf(request, str(form.get("csrf_token", ""))) raw_title = str(form.get("title", "")) raw_price = str(form.get("price", "")) data, errors = _parse(raw_title, raw_price, True) if errors: return await _render( request, errors=errors, form={"title": raw_title, "price": raw_price}, status_code=400, ) await titles_repo.create_title(str(data["title"]), int(data["price"]), True) return RedirectResponse(url="/titles?saved=created", status_code=303) @router.post("/{title_id}") async def update_title(request: Request, title_id: int) -> Response: form = await request.form() check_csrf(request, str(form.get("csrf_token", ""))) data, errors = _parse( str(form.get("title", "")), str(form.get("price", "")), form.get("visible") is not None, ) if errors: return await _render(request, errors=errors, status_code=400) if not await titles_repo.update_title( title_id, str(data["title"]), int(data["price"]), bool(data["visible"]), ): raise HTTPException(status_code=404, detail="Звание не найдено") return RedirectResponse(url="/titles?saved=updated", status_code=303) @router.post("/{title_id}/delete") async def delete_title(request: Request, title_id: int) -> Response: form = await request.form() check_csrf(request, str(form.get("csrf_token", ""))) used = await titles_repo.usage_count(title_id) if used: return await _render( request, errors=[f"Звание используется {used} игроками — скройте его вместо удаления"], status_code=400, ) if not await titles_repo.delete_title(title_id): raise HTTPException(status_code=404, detail="Звание не найдено") return RedirectResponse(url="/titles?saved=deleted", status_code=303)