""" api_client.py — единое место для всех HTTP-запросов к бэкенду. Не зависит от Flet. Все функции async, возвращают данные или None/ошибку. """ import os from typing import Any import httpx API_URL = os.getenv("API_URL", "http://api:8000") _TIMEOUT = 10 # --------------------------------------------------------------------------- # Auth # --------------------------------------------------------------------------- async def login(username: str, password: str) -> tuple[str | None, str | None]: """ POST /auth/login Возвращает (token, None) при успехе или (None, error_message) при ошибке. """ try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.post( "/auth/login", data={"username": username, "password": password}, ) if resp.status_code == 200: return resp.json()["access_token"], None elif resp.status_code == 401: return None, "Неверный логин или пароль" return None, f"Ошибка сервера: {resp.status_code}" except httpx.ConnectError: return None, "Не удалось подключиться к серверу" except Exception as e: return None, f"Ошибка: {e}" async def get_me(token: str) -> dict | None: """GET /users/me — данные текущего пользователя.""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get( "/users/me", headers={"Authorization": f"Bearer {token}"}, ) if resp.status_code == 200: return resp.json() except Exception: pass return None async def register( username: str, password: str, first_name: str, last_name: str, group_id: str | None = None, organization_id: str | None = None, ) -> str | None: """ POST /auth/register Возвращает None при успехе или строку с ошибкой. """ payload: dict[str, Any] = { "username": username, "password": password, "first_name": first_name, "last_name": last_name, } if group_id: payload["group_id"] = group_id if organization_id: payload["organization_id"] = organization_id try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.post("/auth/register", json=payload) if resp.status_code == 201: return None elif resp.status_code == 400: return resp.json().get("detail", "Ошибка регистрации") return f"Ошибка сервера: {resp.status_code}" except httpx.ConnectError: return "Не удалось подключиться к серверу" except Exception as e: return f"Ошибка: {e}" # --------------------------------------------------------------------------- # Organizations & Groups # --------------------------------------------------------------------------- async def get_organizations() -> list: """GET /organizations/""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get("/organizations/") if resp.status_code == 200: return resp.json() except Exception: pass return [] async def get_groups() -> list: """GET /groups/""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get("/groups/") if resp.status_code == 200: return resp.json() except Exception: pass return [] # --------------------------------------------------------------------------- # Polls # --------------------------------------------------------------------------- async def get_polls() -> list: """GET /polls/ — список всех опросов.""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get("/polls/") if resp.status_code == 200: return resp.json() except Exception: pass return [] async def get_poll(poll_id: str) -> dict | None: """GET /polls/{poll_id} — опрос с вопросами и вариантами ответов.""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get(f"/polls/{poll_id}") if resp.status_code == 200: return resp.json() except Exception: pass return None async def get_response(response_id: str) -> dict | None: """GET /polls/responses/{response_id} — детальный ответ с answers.""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get(f"/polls/responses/{response_id}") if resp.status_code == 200: return resp.json() except Exception: pass return None async def get_radar_result(response_id: str) -> dict | None: """GET /responses/{response_id}/radar — данные для радарной диаграммы.""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get(f"/responses/{response_id}/radar") if resp.status_code == 200: return resp.json() except Exception: pass return None async def get_radar_svg_bytes(response_id: str) -> bytes | None: """GET /responses/{response_id}/radar/image — SVG-файл с диаграммой.""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get(f"/responses/{response_id}/radar/image") if resp.status_code == 200: return resp.content except Exception: pass return None async def submit_response( poll_id: str, answers: list[dict], user_id: str | None = None, group_id: str | None = None, organization_id: str | None = None, ) -> tuple[str | None, str | None]: """ POST /polls/{poll_id}/responses answers: [{"question_id": ..., "choice_id": ..., "text": ...}, ...] Возвращает (response_id, None) или (None, error_message). """ payload: dict[str, Any] = {"answers": answers} if user_id: payload["user_id"] = user_id if group_id: payload["group_id"] = group_id if organization_id: payload["organization_id"] = organization_id try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.post(f"/polls/{poll_id}/responses", json=payload) if resp.status_code == 201: return resp.json()["id"], None return None, f"Ошибка сервера: {resp.status_code}" except httpx.ConnectError: return None, "Не удалось подключиться к серверу" except Exception as e: return None, f"Ошибка: {e}" async def get_user_responses(user_id: str) -> list: """GET /polls/users/{user_id}/responses — ответы конкретного пользователя.""" try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.get(f"/polls/users/{user_id}/responses") if resp.status_code == 200: return resp.json() except Exception: pass return [] async def update_user( user_id: str, token: str, first_name: str | None = None, last_name: str | None = None, group_id: str | None = None, organization_id: str | None = None, ) -> tuple[dict | None, str | None]: """PUT /users/{user_id} — обновить данные пользователя.""" payload: dict[str, Any] = {} if first_name is not None: payload["first_name"] = first_name if last_name is not None: payload["last_name"] = last_name if group_id is not None: payload["group_id"] = group_id if organization_id is not None: payload["organization_id"] = organization_id try: async with httpx.AsyncClient(base_url=API_URL, timeout=_TIMEOUT) as client: resp = await client.put( f"/users/{user_id}", json=payload, headers={"Authorization": f"Bearer {token}"}, ) if resp.status_code == 200: return resp.json(), None return None, f"Ошибка сервера: {resp.status_code}" except httpx.ConnectError: return None, "Не удалось подключиться к серверу" except Exception as e: return None, f"Ошибка: {e}"