From ddb95ae8523707738ef3540159c2dd93a3dd1d10 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 27 Sep 2025 04:01:52 +0300 Subject: [PATCH] patch --- app/controllers/main_controller.py | 2 +- app/core/database.py | 33 ++-- app/core/services/auth_service.py | 4 +- app/locales/en.json | 3 + app/locales/ru.json | 3 + app/ui/views/yobble_home_view.py | 266 +++++++++++------------------ requirements.txt | 2 + 7 files changed, 132 insertions(+), 181 deletions(-) diff --git a/app/controllers/main_controller.py b/app/controllers/main_controller.py index 516a137..2acaf8a 100644 --- a/app/controllers/main_controller.py +++ b/app/controllers/main_controller.py @@ -63,7 +63,7 @@ class MainController(QStackedWidget): print("[Sync] Запускаем предзагрузку прав доступа...") try: # Запускаем асинхронную функцию в текущем потоке - asyncio.run(self.yobble_home_view.preload_permissions()) + asyncio.run(self.yobble_home_view.preload_permissions()) # TODO добавить из sqlite print("[Sync] Предзагрузка прав доступа завершена.") except Exception as e: print(f"[Sync] Ошибка во время предзагрузки прав: {e}") diff --git a/app/core/database.py b/app/core/database.py index fdf8837..a33990f 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -1,4 +1,5 @@ import sqlite3 +import aiosqlite import os from datetime import datetime @@ -104,14 +105,26 @@ def set_last_login(login: str): conn.commit() conn.close() -def get_current_access_token(): - """ - Получает access_token для последней активной сессии. - :return: access_token или None, если сессия не найдена. - """ - last_login = get_last_login() - if not last_login: - return None +# def get_current_access_token(): +# """ +# Получает access_token для последней активной сессии. +# :return: access_token или None, если сессия не найдена. +# """ +# last_login = get_last_login() +# if not last_login: +# return None - session = get_session(last_login) - return session['access_token'] if session else None +# session = get_session(last_login) +# return session['access_token'] if session else None + +async def get_current_access_token(): + async with aiosqlite.connect(DB_PATH) as db: + db.row_factory = aiosqlite.Row + async with db.execute("SELECT value FROM app_state WHERE key = 'last_login'") as cur: + row = await cur.fetchone() + if not row: + return None + last_login = row[0] + async with db.execute('SELECT access_token FROM sessions WHERE login = ?', (last_login,)) as cur: + row = await cur.fetchone() + return row[0] if row else None diff --git a/app/core/services/auth_service.py b/app/core/services/auth_service.py index eadd200..ae85031 100644 --- a/app/core/services/auth_service.py +++ b/app/core/services/auth_service.py @@ -101,9 +101,11 @@ async def get_user_role(access_token: str): url = f"{config.BASE_URL}/v1/user/role" headers = {"Authorization": f"Bearer {access_token}"} + timeout = httpx.Timeout(connect=5.0, read=10.0, write=5.0, pool=5.0) try: - async with httpx.AsyncClient(http2=True) as client: + async with httpx.AsyncClient(http2=True, timeout=timeout) as client: response = await client.get(url, headers=headers) + print("headers", headers,"response", response, "status", response.json().get("status")) if response.status_code == 200: data = response.json() diff --git a/app/locales/en.json b/app/locales/en.json index 2584a76..43e5fdc 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -30,6 +30,9 @@ "Регистрация в данный момент отключена.": "Registration is currently disabled.", "Данные не прошли валидацию. Проверьте длину логина и пароля.": "Data validation failed. Check username and password length.", + "Загрузка...": "Загрузка...", + "Доступ запрещен": "Access denied", + "Login must be between 3 and 32 characters long": "Login must be between 3 and 32 characters long", "Login must not contain whitespace characters": "Login must not contain whitespace characters", "Login must not start with an underscore": "Login must not start with an underscore", diff --git a/app/locales/ru.json b/app/locales/ru.json index 8ff02b0..f419444 100644 --- a/app/locales/ru.json +++ b/app/locales/ru.json @@ -30,6 +30,9 @@ "Регистрация в данный момент отключена.": "Регистрация в данный момент отключена.", "Данные не прошли валидацию. Проверьте длину логина и пароля.": "Данные не прошли валидацию. Проверьте длину логина и пароля.", + "Загрузка...": "Loading...", + "Доступ запрещен": "Доступ запрещен", + "Login must be between 3 and 32 characters long": "Логин должен быть от 3 до 32 символов", "Login must not contain whitespace characters": "Логин не должен содержать пробелы", "Login must not start with an underscore": "Логин не должен начинаться с символа подчёркивания", diff --git a/app/ui/views/yobble_home_view.py b/app/ui/views/yobble_home_view.py index 696cab6..dc44714 100644 --- a/app/ui/views/yobble_home_view.py +++ b/app/ui/views/yobble_home_view.py @@ -1,10 +1,11 @@ import asyncio +import time from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame, QStackedWidget, QSizePolicy, QGraphicsDropShadowEffect, QGraphicsOpacityEffect, QMessageBox ) -from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve +from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve, QTimer from PySide6.QtGui import QColor from app.core.theme import theme_manager @@ -27,6 +28,8 @@ class YobbleHomeView(QWidget): self.setMinimumSize(360, 640) self.permission_cache = set() self.permissions_preloaded = False + self.permissions_preloaded = False + self.permissions_preloaded_last = 0.0 # --- Основной макет --- # Используем QHBoxLayout, чтобы можно было разместить меню и контент рядом @@ -52,7 +55,9 @@ class YobbleHomeView(QWidget): # 3. Нижняя панель навигации self.bottom_bar = self.create_bottom_bar() - self.content_stack.setCurrentIndex(2) + # self.update_tab_selection(2) + # self.content_stack.setCurrentIndex(2) + self.switch_tab(2) content_layout.addWidget(self.bottom_bar) main_layout.addWidget(self.content_widget) @@ -63,6 +68,11 @@ class YobbleHomeView(QWidget): self.update_styles() theme_manager.theme_changed.connect(self.update_styles) + # Анти-спам/кулдауны для прав + self._permission_checking = set() # индекс вкладки, где сейчас идёт проверка + self._denied_recently = {} # {index: bool} — активен кулдаун после "Доступ запрещен" + + def setup_side_menu(self): """Настраивает боковое меню и оверлей.""" # Оверлей для затемнения контента @@ -152,6 +162,7 @@ class YobbleHomeView(QWidget): top_bar_layout.addWidget(self.burger_menu_button) self.title_label = QLabel("Чаты") + #self.title_label = QLabel() self.title_label.setObjectName("TitleLabel") top_bar_layout.addWidget(self.title_label) top_bar_layout.addStretch() @@ -200,7 +211,7 @@ class YobbleHomeView(QWidget): btn_create.setFocusPolicy(Qt.NoFocus) - self.update_tab_selection(2) + #self.update_tab_selection(2) return bottom_bar_widget def create_tab_button(self, icon_text, text, index): @@ -253,12 +264,12 @@ class YobbleHomeView(QWidget): def setup_content_pages(self): # Лента - self.feed_label = QLabel("Загрузка...") + self.feed_label = QLabel(localizer.translate("Загрузка...")) self.feed_label.setAlignment(Qt.AlignCenter) self.content_stack.addWidget(self.feed_label) # Музыка - self.music_label = QLabel("Загрузка...") + self.music_label = QLabel(localizer.translate("Загрузка...")) self.music_label.setAlignment(Qt.AlignCenter) self.content_stack.addWidget(self.music_label) @@ -272,35 +283,64 @@ class YobbleHomeView(QWidget): """Обрабатывает нажатие на кнопку вкладки, проверяя права доступа.""" if index in self.REQUIRED_PERMISSIONS: # сразу переключаем на вкладку (там уже "Загрузка...") - self.switch_tab(index) - # запускаем асинхронную проверку + #self.switch_tab(index) + + if self.permissions_preloaded and self.preload_permissions_first==False: + now = time.time() + elapsed = now - self.permissions_preloaded_last if self.permissions_preloaded_last else float("inf") + # если прошло больше 30 секунд или системное время ушло назад → протухло + if elapsed >= 30 or elapsed < 0: + + self.permissions_preloaded = False asyncio.create_task( self.check_permissions_and_switch(index, self.REQUIRED_PERMISSIONS[index]) ) else: - # Для вкладок без специальных прав доступа self.switch_tab(index) async def preload_permissions(self): """Асинхронно предзагружает права доступа без UI.""" - access_token = get_current_access_token() + access_token = await get_current_access_token() if not access_token: print("[Permissions] Preload failed: No access token.") return success, data = await get_user_role(access_token) if success: - user_permissions = data.get("user_permissions", []) - for permission_code in self.REQUIRED_PERMISSIONS.values(): - if permission_code in user_permissions: - self.permission_cache.add(permission_code) + user_permissions = set(data.get("user_permissions", [])) + + # Загружаем ВСЕ права пользователя в кэш + self.permission_cache = user_permissions + print("self.permission_cache", self.permission_cache) + + # for permission_code in self.REQUIRED_PERMISSIONS.values(): + # if permission_code in user_permissions: + # self.permission_cache.add(permission_code) + + self.permissions_preloaded_last = time.time() self.permissions_preloaded = True + self.preload_permissions_first = True + print(f"[Permissions] Preloaded. Cache: {self.permission_cache}") else: print(f"[Permissions] Preload failed: {data}") + # def update_current_tab_content(self): + # """Обновляет контент текущей активной вкладки после предзагрузки прав.""" + # current_index = self.content_stack.currentIndex() + + # # Проверяем, требует ли текущая вкладка прав доступа + # if current_index in self.REQUIRED_PERMISSIONS: + # permission_code = self.REQUIRED_PERMISSIONS[current_index] + + # if permission_code in self.permission_cache: + # self.show_real_content(current_index) + # else: + # self.show_denied(current_index) + async def check_permissions_and_switch(self, index, permission_code): """Асинхронно проверяет права и переключает вкладку.""" + self.preload_permissions_first = False if permission_code in self.permission_cache: self.show_real_content(index) self.switch_tab(index) @@ -313,17 +353,16 @@ class YobbleHomeView(QWidget): return # Иначе делаем запрос - access_token = get_current_access_token() + access_token = await get_current_access_token() if not access_token: self.show_error_message(localizer.translate("Сессия не найдена. Пожалуйста, войдите снова.")) return - success, data = await get_user_role(access_token) - print("data", data) - success, data = await get_user_role(access_token) if success and permission_code in data.get("user_permissions", []): - self.permission_cache.add(permission_code) + # self.permission_cache.add(permission_code) + user_permissions = set(data.get("user_permissions", [])) + self.permission_cache.update(user_permissions) self.show_real_content(index) self.switch_tab(index) else: @@ -356,6 +395,7 @@ class YobbleHomeView(QWidget): button.setProperty("selected", button.property("tab_index") == selected_index) button.style().unpolish(button) button.style().polish(button) + button.update() def show_real_content(self, index): if index == 0: @@ -364,7 +404,7 @@ class YobbleHomeView(QWidget): self.music_label.setText("Контент Музыки") def show_denied(self, index): - denied = QLabel("Доступ запрещен") + denied = QLabel(localizer.translate("Доступ запрещен")) denied.setAlignment(Qt.AlignCenter) denied.setStyleSheet("font-size: 18px; color: #8e8e93;") @@ -399,17 +439,28 @@ class YobbleHomeView(QWidget): """Возвращает QSS стили для компонента в зависимости от темы.""" is_dark = theme_manager.is_dark() - # Цветовая палитра - bg_color = "#1c1c1e" if is_dark else "white" - bar_bg_color = "#2c2c2e" if is_dark else "#f8f8f8" - bar_border_color = "#3c3c3c" if is_dark else "#e7e7e7" - text_color = "#8e8e93" if is_dark else "#888888" - title_color = "white" if is_dark else "black" - active_color = "#0A84FF" - top_bar_bg = "#2c2c2e" if is_dark else "#f5f5f5" - top_bar_border = "#3c3c3c" if is_dark else "#e0e0e0" - hover_color = "#d0d0d0" if not is_dark else "#444444" - overlay_color = "rgba(0, 0, 0, 0.5)" + # Базовая палитра + bg_color = "#1c1c1e" if is_dark else "white" + bar_bg_color = "#2c2c2e" if is_dark else "#f8f8f8" + bar_border_color= "#3c3c3c" if is_dark else "#e7e7e7" + text_color = "#8e8e93" if is_dark else "#777777" + title_color = "white" if is_dark else "black" + top_bar_bg = "#2c2c2e" if is_dark else "#f5f5f5" + top_bar_border = "#3c3c3c" if is_dark else "#e0e0e0" + overlay_color = "rgba(0, 0, 0, 0.5)" + + # Акцент и производные + active_hex = "#0A84FF" + active_rgb = "10, 132, 255" # для rgba() + + # Hover — нейтральный, чтобы не спорил с выбранным + hover_bg = "rgba(0, 0, 0, 0.06)" if not is_dark else "rgba(255, 255, 255, 0.07)" + # Pressed — одинаково в темах + pressed_bg = f"rgba({active_rgb}, 0.36)" + + # Selected — РАЗНЫЕ для светлой и тёмной тем + selected_bg = f"rgba({active_rgb}, 0.16)" if not is_dark else f"rgba({active_rgb}, 0.28)" + selected_border = f"rgba({active_rgb}, 0.24)" if not is_dark else f"rgba({active_rgb}, 0.42)" return f""" #content_widget {{ @@ -419,14 +470,13 @@ class YobbleHomeView(QWidget): background-color: {overlay_color}; }} - /* Глобальные стили для кнопок */ + /* Глобально для кнопок */ QPushButton {{ background: transparent; border: none; outline: none; }} QPushButton:focus, - QPushButton:pressed, QPushButton:checked {{ background: transparent; border: none; @@ -464,29 +514,34 @@ class YobbleHomeView(QWidget): /* Кнопки вкладок */ #TabButton {{ - background: transparent; - border: none; - outline: none; - padding: 5px; + padding: 6px 8px; + border-radius: 10px; + border: 1px solid transparent; /* чтобы при selection не прыгала высота */ }} #TabButton:hover {{ - background-color: {hover_color}; - border-radius: 6px; + background-color: {hover_bg}; }} #TabButton:pressed {{ - background-color: {active_color}22; /* активный цвет с прозрачностью */ - border-radius: 6px; + background-color: {pressed_bg}; + padding-top: 8px; + padding-bottom: 4px; }} + + /* Иконка/текст по умолчанию */ #TabButton #TabIcon {{ color: {text_color}; }} #TabButton #TabText {{ color: {text_color}; }} + /* ВЫБРАННАЯ вкладка — разные тона для light/dark */ #TabButton[selected="true"] {{ - background-color: {active_color}22; + background-color: {selected_bg}; + border: 1px solid {selected_border}; }} #TabButton[selected="true"] #TabIcon, #TabButton[selected="true"] #TabText {{ - color: {active_color}; + color: {active_hex}; + font-weight: 600; }} + #TabIcon, #TabText {{ border: none; outline: none; @@ -521,130 +576,3 @@ class YobbleHomeView(QWidget): ); }} """ - - # def get_stylesheet(self): - # """Возвращает QSS стили для компонента в зависимости от темы.""" - # is_dark = theme_manager.is_dark() - - # # Цветовая палитра - # bg_color = "#1c1c1e" if is_dark else "white" - # bar_bg_color = "#2c2c2e" if is_dark else "#f8f8f8" - # bar_border_color = "#3c3c3c" if is_dark else "#e7e7e7" - # text_color = "#8e8e93" if is_dark else "#888888" - # title_color = "white" if is_dark else "black" - # active_color = "#0A84FF" - # top_bar_bg = "#2c2c2e" if is_dark else "#f5f5f5" - # top_bar_border = "#3c3c3c" if is_dark else "#e0e0e0" - # hover_color = "#3a3a3c" if is_dark else "#e0e0e0" - # overlay_color = "rgba(0, 0, 0, 0.5)" - - # return f""" - # #content_widget {{ - # background-color: {bg_color}; - # }} - # #Overlay {{ - # background-color: {overlay_color}; - # }} - - # /* Глобальные стили для кнопок */ - # QPushButton {{ - # background: transparent; - # border: none; - # outline: none; - # transition: background-color 200ms ease, transform 150ms ease; - # }} - # QPushButton:focus, - # QPushButton:checked {{ - # background: transparent; - # border: none; - # outline: none; - # }} - - # /* Верхняя панель */ - # #TopBar {{ - # background-color: {top_bar_bg}; - # border-bottom: 1px solid {top_bar_border}; - # }} - # #TopBar QPushButton {{ - # font-size: 22px; - # border: none; - # padding: 5px; - # color: {title_color}; - # background: transparent; - # }} - # #TitleLabel {{ - # font-size: 18px; - # font-weight: bold; - # color: {title_color}; - # border: none; - # outline: none; - # background-color: transparent; - # }} - - # /* Нижняя панель */ - # #BottomBar {{ - # background-color: {bar_bg_color}; - # border-top: 1px solid {bar_border_color}; - # padding-top: 5px; - # padding-bottom: 15px; - # }} - - # /* Кнопки вкладок */ - # #TabButton {{ - # border-radius: 6px; - # padding: 6px; - # }} - # /* Hover (не выбранная вкладка) */ - # #TabButton:hover[selected="false"] {{ - # background-color: {hover_color}; - # }} - # /* Pressed (удержание) */ - # #TabButton:pressed {{ - # background-color: {active_color}44; - # transform: scale(0.95); - # }} - # /* Selected */ - # #TabButton[selected="true"] {{ - # background-color: {active_color}22; - # }} - # #TabButton[selected="true"] #TabIcon, - # #TabButton[selected="true"] #TabText {{ - # color: {active_color}; - # font-weight: 600; - # }} - - # #TabIcon, #TabText {{ - # border: none; - # outline: none; - # background-color: transparent; - # }} - # #TabIcon {{ font-size: 22px; color: {text_color}; }} - # #TabText {{ font-size: 12px; color: {text_color}; }} - - # /* Центральная кнопка "Создать" */ - # #CreateButton {{ - # color: white; - # font-size: 30px; - # font-weight: 300; - # border: none; - # border-radius: 28px; - # background-color: qlineargradient( - # x1: 0, y1: 0, x2: 0, y2: 1, - # stop: 0 #007AFF, stop: 1 #0056b3 - # ); - # margin-bottom: 20px; - # }} - # #CreateButton:hover {{ - # background-color: qlineargradient( - # x1: 0, y1: 0, x2: 0, y2: 1, - # stop: 0 #0088FF, stop: 1 #0066c3 - # ); - # }} - # #CreateButton:pressed {{ - # background-color: qlineargradient( - # x1: 0, y1: 0, x2: 0, y2: 1, - # stop: 0 #0056b3, stop: 1 #004493 - # ); - # transform: scale(0.95); - # }} - # """ diff --git a/requirements.txt b/requirements.txt index 26a7ac8..3677f52 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,5 @@ common-lib @ git+https://githlam.com/messenger/common_lib.git@main httpx[http2]==0.28.1 asyncio==4.0.0 qasync==0.28.0 +logging==0.4.9.6 +aiosqlite==0.21.0