patch chat
This commit is contained in:
		
							parent
							
								
									6f8bc0e762
								
							
						
					
					
						commit
						22b6fd272f
					
				@ -105,7 +105,7 @@ class MainController(QStackedWidget):
 | 
				
			|||||||
        #     return
 | 
					        #     return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        token = session['access_token']
 | 
					        token = session['access_token']
 | 
				
			||||||
        success, data = await get_private_chats(token=token, offset=0, limit=50)
 | 
					        success, data = await get_private_chats(login=username, token=token, offset=0, limit=50)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if success:
 | 
					        if success:
 | 
				
			||||||
            # Отправляем данные в основной поток через сигнал
 | 
					            # Отправляем данные в основной поток через сигнал
 | 
				
			||||||
@ -113,3 +113,5 @@ class MainController(QStackedWidget):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # Отправляем ошибку в основной поток через сигнал
 | 
					            # Отправляем ошибку в основной поток через сигнал
 | 
				
			||||||
            self.notification_requested.emit(f"Не удалось загрузить чаты: {data}", True)
 | 
					            self.notification_requested.emit(f"Не удалось загрузить чаты: {data}", True)
 | 
				
			||||||
 | 
					            print("data", data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,92 @@
 | 
				
			|||||||
import ssl
 | 
					 | 
				
			||||||
import httpx
 | 
					import httpx
 | 
				
			||||||
 | 
					from contextvars import ContextVar
 | 
				
			||||||
 | 
					from typing import Dict, Any, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ssl_transport = httpx.AsyncHTTPTransport(http2=True)
 | 
					# Connection pool & timeouts
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Ограничения пула соединений
 | 
					 | 
				
			||||||
limits = httpx.Limits(max_connections=200, max_keepalive_connections=50)
 | 
					limits = httpx.Limits(max_connections=200, max_keepalive_connections=50)
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Таймауты
 | 
					 | 
				
			||||||
timeout = httpx.Timeout(connect=5.0, read=10.0, write=5.0, pool=5.0)
 | 
					timeout = httpx.Timeout(connect=5.0, read=10.0, write=5.0, pool=5.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Глобальный клиент
 | 
					# Keep a client per event loop/context to avoid cross-loop issues
 | 
				
			||||||
client = httpx.AsyncClient(
 | 
					_client_ctx: ContextVar[httpx.AsyncClient | None] = ContextVar("http_client_ctx", default=None)
 | 
				
			||||||
    transport=ssl_transport,
 | 
					
 | 
				
			||||||
    limits=limits,
 | 
					def get_client() -> httpx.AsyncClient:
 | 
				
			||||||
    timeout=timeout,
 | 
					    client = _client_ctx.get()
 | 
				
			||||||
)
 | 
					    if client is None or client.is_closed:
 | 
				
			||||||
 | 
					        transport = httpx.AsyncHTTPTransport(http2=True)
 | 
				
			||||||
 | 
					        client = httpx.AsyncClient(transport=transport, limits=limits, timeout=timeout)
 | 
				
			||||||
 | 
					        _client_ctx.set(client)
 | 
				
			||||||
 | 
					    return client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def aclose_client():
 | 
				
			||||||
 | 
					    client = _client_ctx.get()
 | 
				
			||||||
 | 
					    if client is not None and not client.is_closed:
 | 
				
			||||||
 | 
					        await client.aclose()
 | 
				
			||||||
 | 
					        _client_ctx.set(None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# --- Authorized helpers with auto-refresh on 401 ---
 | 
				
			||||||
 | 
					async def authorized_get(
 | 
				
			||||||
 | 
					    url: str,
 | 
				
			||||||
 | 
					    *,
 | 
				
			||||||
 | 
					    login: Optional[str] = None,
 | 
				
			||||||
 | 
					    access_token: Optional[str] = None,
 | 
				
			||||||
 | 
					    headers: Optional[Dict[str, str]] = None,
 | 
				
			||||||
 | 
					    params: Optional[Dict[str, Any]] = None,
 | 
				
			||||||
 | 
					) -> httpx.Response:
 | 
				
			||||||
 | 
					    hdrs = dict(headers or {})
 | 
				
			||||||
 | 
					    if access_token:
 | 
				
			||||||
 | 
					        hdrs["Authorization"] = f"Bearer {access_token}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resp = await get_client().get(url, headers=hdrs, params=params)
 | 
				
			||||||
 | 
					    if resp.status_code != 401 or not login or not access_token:
 | 
				
			||||||
 | 
					        return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Try refresh flow lazily to avoid import cycle at import time
 | 
				
			||||||
 | 
					    from app.core.database import get_session, logout
 | 
				
			||||||
 | 
					    from app.core.services.auth_service import refresh_token as do_refresh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session = get_session(login)
 | 
				
			||||||
 | 
					    if not session or not session.get("refresh_token"):
 | 
				
			||||||
 | 
					        return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ok, data = await do_refresh(access_token, session["refresh_token"])
 | 
				
			||||||
 | 
					    if ok:
 | 
				
			||||||
 | 
					        new_access = data["access_token"]
 | 
				
			||||||
 | 
					        hdrs["Authorization"] = f"Bearer {new_access}"
 | 
				
			||||||
 | 
					        return await get_client().get(url, headers=hdrs, params=params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logout(access_token)
 | 
				
			||||||
 | 
					    return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def authorized_post(
 | 
				
			||||||
 | 
					    url: str,
 | 
				
			||||||
 | 
					    *,
 | 
				
			||||||
 | 
					    login: Optional[str] = None,
 | 
				
			||||||
 | 
					    access_token: Optional[str] = None,
 | 
				
			||||||
 | 
					    headers: Optional[Dict[str, str]] = None,
 | 
				
			||||||
 | 
					    params: Optional[Dict[str, Any]] = None,
 | 
				
			||||||
 | 
					    json: Any = None,
 | 
				
			||||||
 | 
					    data: Any = None,
 | 
				
			||||||
 | 
					) -> httpx.Response:
 | 
				
			||||||
 | 
					    hdrs = dict(headers or {})
 | 
				
			||||||
 | 
					    if access_token:
 | 
				
			||||||
 | 
					        hdrs["Authorization"] = f"Bearer {access_token}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resp = await get_client().post(url, headers=hdrs, params=params, json=json, data=data)
 | 
				
			||||||
 | 
					    if resp.status_code != 401 or not login or not access_token:
 | 
				
			||||||
 | 
					        return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from app.core.database import get_session, logout
 | 
				
			||||||
 | 
					    from app.core.services.auth_service import refresh_token as do_refresh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session = get_session(login)
 | 
				
			||||||
 | 
					    if not session or not session.get("refresh_token"):
 | 
				
			||||||
 | 
					        return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ok, data = await do_refresh(access_token, session["refresh_token"])
 | 
				
			||||||
 | 
					    if ok:
 | 
				
			||||||
 | 
					        new_access = data["access_token"]
 | 
				
			||||||
 | 
					        hdrs["Authorization"] = f"Bearer {new_access}"
 | 
				
			||||||
 | 
					        return await get_client().post(url, headers=hdrs, params=params, json=json, data=data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logout(access_token)
 | 
				
			||||||
 | 
					    return resp
 | 
				
			||||||
 | 
				
			|||||||
@ -3,62 +3,61 @@ import asyncio
 | 
				
			|||||||
from app.core import config
 | 
					from app.core import config
 | 
				
			||||||
from app.core.database import add_session, logout, get_session
 | 
					from app.core.database import add_session, logout, get_session
 | 
				
			||||||
from app.core.localizer import localizer
 | 
					from app.core.localizer import localizer
 | 
				
			||||||
 | 
					from app.core.http_client import get_client, authorized_get
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def login(login, password):
 | 
					async def login(login, password):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Отправляет запрос на аутентификацию и в случае успеха сохраняет сессию.
 | 
					    Логин пользователя по логину и паролю.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param login: Логин пользователя
 | 
					    :param login: Логин пользователя
 | 
				
			||||||
    :param password: Пароль пользователя
 | 
					    :param password: Пароль пользователя
 | 
				
			||||||
    :return: Кортеж (успех: bool, сообщение: str)
 | 
					    :return: tuple (ok: bool, message: str)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    url = f"{config.BASE_URL}/v1/auth/login"
 | 
					    url = f"{config.BASE_URL}/v1/auth/login"
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
					        response = await get_client().post(url, json={"login": login, "password": password})
 | 
				
			||||||
            response = await client.post(url, json={"login": login, "password": password})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if response.status_code == 200:
 | 
					        if response.status_code == 200:
 | 
				
			||||||
                data = response.json()
 | 
					            data = response.json()
 | 
				
			||||||
                if data.get("status") == "fine":
 | 
					            if data.get("status") == "fine":
 | 
				
			||||||
                    token_data = data["data"]
 | 
					                token_data = data["data"]
 | 
				
			||||||
                    add_session(
 | 
					                add_session(
 | 
				
			||||||
                        login=login,
 | 
					                    login=login,
 | 
				
			||||||
                        access_token=token_data["access_token"],
 | 
					                    access_token=token_data["access_token"],
 | 
				
			||||||
                        refresh_token=token_data["refresh_token"],
 | 
					                    refresh_token=token_data["refresh_token"],
 | 
				
			||||||
                        user_id=token_data["user_id"]
 | 
					                    user_id=token_data.get("user_id"),
 | 
				
			||||||
                    )
 | 
					                )
 | 
				
			||||||
                    return True, localizer.translate("Успешный вход")
 | 
					                return True, localizer.translate("Вход выполнен успешно")
 | 
				
			||||||
                else:
 | 
					            return False, data.get("detail", localizer.translate("Неизвестная ошибка"))
 | 
				
			||||||
                    return False, data.get("detail", localizer.translate("Неизвестная ошибка ответа"))
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            elif response.status_code in [401]:
 | 
					 | 
				
			||||||
                error_data = response.json()
 | 
					 | 
				
			||||||
                return False, error_data.get("detail", localizer.translate("Неверный логин или пароль"))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif response.status_code in [403]:
 | 
					        if response.status_code == 401:
 | 
				
			||||||
                error_data = response.json()
 | 
					            error_data = response.json()
 | 
				
			||||||
                return False, error_data.get("detail", localizer.translate("Учетная запись пользователя отключена"))
 | 
					            return False, error_data.get("detail", localizer.translate("Неверный логин или пароль"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif response.status_code == 422:
 | 
					        if response.status_code == 403:
 | 
				
			||||||
                return False, localizer.translate("Некорректные данные для входа")
 | 
					            error_data = response.json()
 | 
				
			||||||
 | 
					            return False, error_data.get("detail", localizer.translate("Доступ запрещен"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            else:
 | 
					        if response.status_code == 422:
 | 
				
			||||||
                return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
					            return False, localizer.translate("Некорректные входные данные")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except httpx.RequestError as e:
 | 
					    except httpx.RequestError as e:
 | 
				
			||||||
        return False, f"{localizer.translate('Ошибка сети')}: {e}"
 | 
					        return False, f"{localizer.translate('Сетевая ошибка')}: {e}"
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
					        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def register(login, password, invite=None):
 | 
					async def register(login, password, invite=None):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Отправляет запрос на регистрацию нового пользователя.
 | 
					    Регистрация нового пользователя.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param login: Логин
 | 
					    :param login: Логин
 | 
				
			||||||
    :param password: Пароль
 | 
					    :param password: Пароль
 | 
				
			||||||
    :param invite: Инвайт-код (опционально)
 | 
					    :param invite: Инвайт-код (опционально)
 | 
				
			||||||
    :return: Кортеж (успех: bool, сообщение: str)
 | 
					    :return: tuple (ok: bool, message: str)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    url = f"{config.BASE_URL}/v1/auth/register"
 | 
					    url = f"{config.BASE_URL}/v1/auth/register"
 | 
				
			||||||
    payload = {"login": login, "password": password}
 | 
					    payload = {"login": login, "password": password}
 | 
				
			||||||
@ -66,116 +65,90 @@ async def register(login, password, invite=None):
 | 
				
			|||||||
        payload["invite"] = invite
 | 
					        payload["invite"] = invite
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
					        response = await get_client().post(url, json=payload)
 | 
				
			||||||
            response = await client.post(url, json=payload)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if response.status_code == 201:
 | 
					        if response.status_code == 201:
 | 
				
			||||||
                return True, localizer.translate("Регистрация прошла успешно!")
 | 
					            return True, localizer.translate("Регистрация прошла успешно!")
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            error_data = response.json()
 | 
					 | 
				
			||||||
            error_message = error_data.get("detail", localizer.translate("Произошла ошибка"))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if response.status_code == 409:
 | 
					        error_data = response.json()
 | 
				
			||||||
                return False, localizer.translate("Этот логин уже занят.")
 | 
					        error_message = error_data.get("detail", localizer.translate("Произошла ошибка"))
 | 
				
			||||||
            elif response.status_code == 400:
 | 
					
 | 
				
			||||||
                return False, localizer.translate("Неверный инвайт-код.")
 | 
					        if response.status_code == 409:
 | 
				
			||||||
            elif response.status_code == 403:
 | 
					            return False, localizer.translate("Пользователь уже существует.")
 | 
				
			||||||
                return False, localizer.translate("Регистрация в данный момент отключена.")
 | 
					        if response.status_code == 400:
 | 
				
			||||||
            elif response.status_code == 422:
 | 
					            return False, localizer.translate("Некорректный запрос.")
 | 
				
			||||||
                 return False, localizer.translate("Данные не прошли валидацию. Проверьте длину логина и пароля.")
 | 
					        if response.status_code == 403:
 | 
				
			||||||
            else:
 | 
					            return False, localizer.translate("Регистрация по приглашению отключена.")
 | 
				
			||||||
                return False, f"{localizer.translate('Ошибка сервера')} ({response.status_code}): {error_message}"
 | 
					        if response.status_code == 422:
 | 
				
			||||||
 | 
					            return False, localizer.translate("Некорректные входные данные. Проверьте логин и пароль.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return False, f"{localizer.translate('Ошибка сервера')} ({response.status_code}): {error_message}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except httpx.RequestError as e:
 | 
					    except httpx.RequestError as e:
 | 
				
			||||||
        return False, f"{localizer.translate('Ошибка сети')}: {e}"
 | 
					        return False, f"{localizer.translate('Сетевая ошибка')}: {e}"
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
					        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def refresh_token(access_token: str, refresh_token: str):
 | 
					async def refresh_token(access_token: str, refresh_token: str):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Обновляет токен доступа, используя токен обновления.
 | 
					    Обновление пары токенов по refresh_token.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param access_token: Истекший токен доступа
 | 
					    :param access_token: Текущий access token
 | 
				
			||||||
    :param refresh_token: Токен обновления
 | 
					    :param refresh_token: Refresh token
 | 
				
			||||||
    :return: Кортеж (успех: bool, данные: dict | str)
 | 
					    :return: tuple (ok: bool, data: dict | str)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    url = f"{config.BASE_URL}/v1/auth/token/refresh"
 | 
					    url = f"{config.BASE_URL}/v1/auth/token/refresh"
 | 
				
			||||||
    payload = {"access_token": access_token, "refresh_token": refresh_token}
 | 
					    payload = {"access_token": access_token, "refresh_token": refresh_token}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
					        response = await get_client().post(url, json=payload)
 | 
				
			||||||
            response = await client.post(url, json=payload)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if response.status_code == 200:
 | 
					        if response.status_code == 200:
 | 
				
			||||||
                data = response.json()
 | 
					            data = response.json()
 | 
				
			||||||
                if data.get("status") == "fine":
 | 
					            if data.get("status") == "fine":
 | 
				
			||||||
                    token_data = data["data"]
 | 
					                token_data = data["data"]
 | 
				
			||||||
                    # Обновляем сессию с новыми токенами
 | 
					                add_session(
 | 
				
			||||||
                    add_session(
 | 
					                    login=None,
 | 
				
			||||||
                        login=None,  # Логин не требуется для обновления
 | 
					                    access_token=token_data["access_token"],
 | 
				
			||||||
                        access_token=token_data["access_token"],
 | 
					                    refresh_token=token_data["refresh_token"],
 | 
				
			||||||
                        refresh_token=token_data["refresh_token"],
 | 
					                    update_existing=True,
 | 
				
			||||||
                        update_existing=True
 | 
					                )
 | 
				
			||||||
                    )
 | 
					                return True, token_data
 | 
				
			||||||
                    return True, token_data
 | 
					            return False, data.get("detail", localizer.translate("Неизвестная ошибка"))
 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    return False, data.get("detail", localizer.translate("Unknown error"))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif response.status_code == 401:
 | 
					        if response.status_code == 401:
 | 
				
			||||||
                return False, localizer.translate("Refresh token is invalid or expired")
 | 
					            return False, localizer.translate("Refresh token недействителен или истек")
 | 
				
			||||||
            
 | 
					
 | 
				
			||||||
            else:
 | 
					        return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
				
			||||||
                return False, f"{localizer.translate('Server error')}: {response.status_code}"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except httpx.RequestError as e:
 | 
					    except httpx.RequestError as e:
 | 
				
			||||||
        return False, f"{localizer.translate('Network error')}: {e}"
 | 
					        return False, f"{localizer.translate('Сетевая ошибка')}: {e}"
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return False, f"{localizer.translate('An error occurred')}: {e}"
 | 
					        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def get_user_role(access_token: str, login: str):
 | 
					async def get_user_role(access_token: str, login: str):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Получает роль и права пользователя по токену доступа.
 | 
					    Получение роли пользователя. При 401 выполняется автообновление токена внутри authorized_get.
 | 
				
			||||||
    В случае истечения срока действия токена, пытается его обновить.
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    url = f"{config.BASE_URL}/v1/user/role"
 | 
					    url = f"{config.BASE_URL}/v1/user/role"
 | 
				
			||||||
    headers = {"Authorization": f"Bearer {access_token}"}
 | 
					    headers = {"Authorization": f"Bearer {access_token}"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
					        response = await authorized_get(url, login=login, access_token=access_token, headers=headers)
 | 
				
			||||||
            response = await client.get(url, headers=headers)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if response.status_code == 200:
 | 
					        if response.status_code == 200:
 | 
				
			||||||
                data = response.json()
 | 
					            data = response.json()
 | 
				
			||||||
                return (True, data['data']) if data.get("status") == "fine" else (False, data.get("detail", localizer.translate("Unknown error")))
 | 
					            return (True, data['data']) if data.get("status") == "fine" else (False, data.get("detail", localizer.translate("Неизвестная ошибка")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif response.status_code == 401:
 | 
					        if response.status_code == 401:
 | 
				
			||||||
                # Токен истек, пытаемся обновить
 | 
					            return False, localizer.translate("Сессия истекла, войдите снова")
 | 
				
			||||||
                session = get_session(login)
 | 
					 | 
				
			||||||
                if not session or not session['refresh_token']:
 | 
					 | 
				
			||||||
                    return False, localizer.translate("No refresh token found")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                refresh_success, refresh_data = await refresh_token(access_token, session['refresh_token'])
 | 
					        return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if refresh_success:
 | 
					 | 
				
			||||||
                    # Повторяем запрос с новым токеном
 | 
					 | 
				
			||||||
                    new_access_token = refresh_data['access_token']
 | 
					 | 
				
			||||||
                    headers["Authorization"] = f"Bearer {new_access_token}"
 | 
					 | 
				
			||||||
                    response = await client.get(url, headers=headers)
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    if response.status_code == 200:
 | 
					 | 
				
			||||||
                        data = response.json()
 | 
					 | 
				
			||||||
                        return (True, data['data']) if data.get("status") == "fine" else (False, localizer.translate("Failed to get role after refresh"))
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                # Если обновление не удалось, выходим из системы
 | 
					 | 
				
			||||||
                logout(access_token)
 | 
					 | 
				
			||||||
                return False, localizer.translate("Session expired, please log in again")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                return False, f"{localizer.translate('Server error')}: {response.status_code}"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except httpx.RequestError as e:
 | 
					    except httpx.RequestError as e:
 | 
				
			||||||
        return False, f"{localizer.translate('Network error')}: {e}"
 | 
					        return False, f"{localizer.translate('Сетевая ошибка')}: {e}"
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return False, f"{localizer.translate('An error occurred')}: {e}"
 | 
					        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,61 +7,49 @@ from app.core.models.chat_models import (
 | 
				
			|||||||
    PrivateMessageSendRequest, PrivateMessageSendResponse
 | 
					    PrivateMessageSendRequest, PrivateMessageSendResponse
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from uuid import UUID
 | 
					from uuid import UUID
 | 
				
			||||||
 | 
					from app.core.http_client import get_client, authorized_get, authorized_post
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def get_private_chats(token: str, offset: int = 0, limit: int = 20):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Получает список приватных чатов пользователя.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param token: Токен доступа пользователя
 | 
					async def get_private_chats(login: str, token: str, offset: int = 0, limit: int = 20):
 | 
				
			||||||
    :param offset: Смещение для пагинации
 | 
					    """
 | 
				
			||||||
    :param limit: Количество чатов для загрузки
 | 
					    Получить список приватных чатов.
 | 
				
			||||||
    :return: Кортеж (успех: bool, данные: PrivateChatListData | str)
 | 
					
 | 
				
			||||||
 | 
					    :param token: Bearer токен
 | 
				
			||||||
 | 
					    :param offset: Смещение
 | 
				
			||||||
 | 
					    :param limit: Количество
 | 
				
			||||||
 | 
					    :return: tuple (ok: bool, data: PrivateChatListData | str)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # TODO: Добавить логику обновления токена, как в auth_service.py
 | 
					 | 
				
			||||||
    url = f"{config.BASE_URL}/v1/chat/private/list"
 | 
					    url = f"{config.BASE_URL}/v1/chat/private/list"
 | 
				
			||||||
    headers = {"Authorization": f"Bearer {token}"}
 | 
					    headers = {"Authorization": f"Bearer {token}"}
 | 
				
			||||||
    params = {"offset": offset, "limit": limit}
 | 
					    params = {"offset": offset, "limit": limit}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
					        response = await authorized_get(url, login=login, access_token=token, headers=headers, params=params)
 | 
				
			||||||
            response = await client.get(url, headers=headers, params=params)
 | 
					        if response.status_code == 200:
 | 
				
			||||||
            print("response.status_code", response.status_code)
 | 
					            data = response.json()
 | 
				
			||||||
 | 
					            if data.get("status") == "fine":
 | 
				
			||||||
 | 
					                response_model = PrivateChatListResponse(**data)
 | 
				
			||||||
 | 
					                return True, response_model.data
 | 
				
			||||||
 | 
					            return False, data.get("detail", localizer.translate("Неизвестная ошибка"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if response.status_code == 200:
 | 
					        if response.status_code in [401, 403]:
 | 
				
			||||||
                data = response.json()
 | 
					            error_data = response.json()
 | 
				
			||||||
                if data.get("status") == "fine":
 | 
					            return False, error_data.get("detail", localizer.translate("Недостаточно прав или неавторизован"))
 | 
				
			||||||
                    # Используем Pydantic модель для парсинга ответа
 | 
					 | 
				
			||||||
                    response_model = PrivateChatListResponse(**data)
 | 
					 | 
				
			||||||
                    # print("response_model.data", response_model.data)
 | 
					 | 
				
			||||||
                    print("data", data)
 | 
					 | 
				
			||||||
                    return True, response_model.data
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    return False, data.get("detail", localizer.translate("Неизвестная ошибка ответа"))
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            elif response.status_code in [401, 403]:
 | 
					 | 
				
			||||||
                error_data = response.json()
 | 
					 | 
				
			||||||
                return False, error_data.get("detail", localizer.translate("Ошибка аутентификации или авторизации"))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif response.status_code == 422:
 | 
					        if response.status_code == 422:
 | 
				
			||||||
                return False, localizer.translate("Некорректные параметры запроса")
 | 
					            return False, localizer.translate("Некорректные параметры запроса")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            else:
 | 
					        return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
				
			||||||
                return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except httpx.RequestError as e:
 | 
					    except httpx.RequestError as e:
 | 
				
			||||||
        return False, f"{localizer.translate('Ошибка сети')}: {e}"
 | 
					        return False, f"{localizer.translate('Сетевая ошибка')}: {e}"
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
					        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def get_chat_history(token: str, chat_id: UUID, before_message_id: int = None, limit: int = 30):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Получает историю сообщений для указанного приватного чата.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param token: Токен доступа
 | 
					async def get_chat_history(login: str, token: str, chat_id: UUID, before_message_id: int = None, limit: int = 30):
 | 
				
			||||||
    :param chat_id: ID чата
 | 
					    """
 | 
				
			||||||
    :param before_message_id: ID сообщения для пагинации (загрузка более старых)
 | 
					    История сообщений приватного чата.
 | 
				
			||||||
    :param limit: Количество сообщений для загрузки
 | 
					 | 
				
			||||||
    :return: Кортеж (успех: bool, данные: PrivateChatHistoryData | str)
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    url = f"{config.BASE_URL}/v1/chat/private/history"
 | 
					    url = f"{config.BASE_URL}/v1/chat/private/history"
 | 
				
			||||||
    headers = {"Authorization": f"Bearer {token}"}
 | 
					    headers = {"Authorization": f"Bearer {token}"}
 | 
				
			||||||
@ -70,73 +58,61 @@ async def get_chat_history(token: str, chat_id: UUID, before_message_id: int = N
 | 
				
			|||||||
        params["before_message_id"] = before_message_id
 | 
					        params["before_message_id"] = before_message_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
					        response = await authorized_get(url, login=login, access_token=token, headers=headers, params=params)
 | 
				
			||||||
            response = await client.get(url, headers=headers, params=params)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if response.status_code == 200:
 | 
					        if response.status_code == 200:
 | 
				
			||||||
                data = response.json()
 | 
					            data = response.json()
 | 
				
			||||||
                if data.get("status") == "fine":
 | 
					            if data.get("status") == "fine":
 | 
				
			||||||
                    response_model = PrivateChatHistoryResponse(**data)
 | 
					                response_model = PrivateChatHistoryResponse(**data)
 | 
				
			||||||
                    return True, response_model.data
 | 
					                return True, response_model.data
 | 
				
			||||||
                else:
 | 
					            return False, data.get("detail", localizer.translate("Неизвестная ошибка"))
 | 
				
			||||||
                    return False, data.get("detail", "Неизвестная ошибка ответа")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif response.status_code in [401, 403]:
 | 
					        if response.status_code in [401, 403]:
 | 
				
			||||||
                error_data = response.json()
 | 
					            error_data = response.json()
 | 
				
			||||||
                return False, error_data.get("detail", "Ошибка аутентификации или доступа")
 | 
					            return False, error_data.get("detail", localizer.translate("Недостаточно прав или неавторизован"))
 | 
				
			||||||
            
 | 
					        
 | 
				
			||||||
            elif response.status_code == 422:
 | 
					        if response.status_code == 422:
 | 
				
			||||||
                return False, "Некорректные параметры запроса"
 | 
					            return False, localizer.translate("Некорректные параметры запроса")
 | 
				
			||||||
            
 | 
					        
 | 
				
			||||||
            else:
 | 
					        return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
				
			||||||
                return False, f"Ошибка сервера: {response.status_code}"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except httpx.RequestError as e:
 | 
					    except httpx.RequestError as e:
 | 
				
			||||||
        return False, f"Ошибка сети: {e}"
 | 
					        return False, f"{localizer.translate('Сетевая ошибка')}: {e}"
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return False, f"Произошла ошибка: {e}"
 | 
					        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def send_private_message(token: str, payload: "PrivateMessageSendRequest"):
 | 
					async def send_private_message(login: str, token: str, payload: "PrivateMessageSendRequest"):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Отправляет приватное сообщение в чат.
 | 
					    Отправка приватного сообщения.
 | 
				
			||||||
 | 
					 | 
				
			||||||
    :param token: Токен доступа
 | 
					 | 
				
			||||||
    :param payload: Данные сообщения (Pydantic модель PrivateMessageSendRequest)
 | 
					 | 
				
			||||||
    :return: Кортеж (успех: bool, данные: PrivateMessageSendData | str)
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    url = f"{config.BASE_URL}/v1/chat/private/send"
 | 
					    url = f"{config.BASE_URL}/v1/chat/private/send"
 | 
				
			||||||
    headers = {"Authorization": f"Bearer {token}"}
 | 
					    headers = {"Authorization": f"Bearer {token}"}
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
					        response = await authorized_post(url, login=login, access_token=token, headers=headers, json=payload.model_dump(mode='json'))
 | 
				
			||||||
            response = await client.post(url, headers=headers, json=payload.model_dump(mode='json'))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if response.status_code == 200:
 | 
					        if response.status_code == 200:
 | 
				
			||||||
                data = response.json()
 | 
					            data = response.json()
 | 
				
			||||||
                if data.get("status") == "fine":
 | 
					            if data.get("status") == "fine":
 | 
				
			||||||
                    response_model = PrivateMessageSendResponse(**data)
 | 
					                response_model = PrivateMessageSendResponse(**data)
 | 
				
			||||||
                    return True, response_model.data
 | 
					                return True, response_model.data
 | 
				
			||||||
                else:
 | 
					            return False, data.get("detail", localizer.translate("Неизвестная ошибка"))
 | 
				
			||||||
                    return False, data.get("detail", "Неизвестная ошибка ответа")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif response.status_code in [401, 403, 404]:
 | 
					        if response.status_code in [401, 403, 404]:
 | 
				
			||||||
                error_data = response.json()
 | 
					            error_data = response.json()
 | 
				
			||||||
                print("error_data", error_data)
 | 
					            return False, error_data.get("detail", localizer.translate("Недостаточно прав или ресурс не найден"))
 | 
				
			||||||
                return False, error_data.get("detail", "Ошибка доступа или чат не найден")
 | 
					        
 | 
				
			||||||
            
 | 
					        if response.status_code == 422:
 | 
				
			||||||
            elif response.status_code == 422:
 | 
					            error_data = response.json()
 | 
				
			||||||
                error_data = response.json()
 | 
					            detail = error_data.get("detail")
 | 
				
			||||||
                # Может быть список ошибок
 | 
					            if isinstance(detail, list):
 | 
				
			||||||
                detail = error_data.get("detail")
 | 
					                return False, ", ".join([e.get("msg", localizer.translate("Некорректные параметры")) for e in detail])
 | 
				
			||||||
                if isinstance(detail, list):
 | 
					            return False, detail or localizer.translate("Некорректный запрос")
 | 
				
			||||||
                    return False, ", ".join([e.get("msg", "Неизвестная ошибка валидации") for e in detail])
 | 
					        
 | 
				
			||||||
                return False, detail or "Некорректные данные"
 | 
					        return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                return False, f"Ошибка сервера: {response.status_code}"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except httpx.RequestError as e:
 | 
					    except httpx.RequestError as e:
 | 
				
			||||||
        return False, f"Ошибка сети: {e}"
 | 
					        return False, f"{localizer.translate('Сетевая ошибка')}: {e}"
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return False, f"Произошла ошибка: {e}"
 | 
					        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
				
			||||||
 | 
				
			|||||||
@ -489,7 +489,7 @@ class YobbleHomeView(QWidget):
 | 
				
			|||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.show_notification("Загрузка истории...", is_error=False, duration=1000)
 | 
					        self.show_notification("Загрузка истории...", is_error=False, duration=1000)
 | 
				
			||||||
        success, data = await get_chat_history(token, chat_id)
 | 
					        success, data = await get_chat_history(self.username, token, chat_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if success:
 | 
					        if success:
 | 
				
			||||||
            if self.chat_view:
 | 
					            if self.chat_view:
 | 
				
			||||||
@ -532,7 +532,7 @@ class YobbleHomeView(QWidget):
 | 
				
			|||||||
            self.show_notification("Ошибка: сессия не найдена.", is_error=True)
 | 
					            self.show_notification("Ошибка: сессия не найдена.", is_error=True)
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        success, data = await send_private_message(token, payload)
 | 
					        success, data = await send_private_message(self.username, token, payload)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if success:
 | 
					        if success:
 | 
				
			||||||
            # В случае успеха, создаем объект сообщения и добавляем в чат
 | 
					            # В случае успеха, создаем объект сообщения и добавляем в чат
 | 
				
			||||||
@ -791,3 +791,4 @@ class YobbleHomeView(QWidget):
 | 
				
			|||||||
                );
 | 
					                );
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user