diff --git a/.gitignore b/.gitignore index a78e733..a45461a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ config/SSL/fullchain.pem config/SSL/privkey.pem logs/ SECRET_KEY.key +messenger.db # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/app/core/config.py b/app/core/config.py index cea9efd..b8f3070 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,5 +1,7 @@ DEBUG = True -BASE_URL = "https://api.yobble.org" +API_SCHEME = "https" +API_HOST = "api.yobble.org" +BASE_URL = f"{API_SCHEME}://{API_HOST}" APP_VERSION = "0.1_login_screen_windows" APP_NAME = "yobble messenger" APP_HEADER = f"{APP_NAME}" diff --git a/app/core/database.py b/app/core/database.py index 22774a6..c657d18 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -1,21 +1,81 @@ import sqlite3 import os +from datetime import datetime DB_PATH = "messenger.db" def get_connection(): - return sqlite3.connect(DB_PATH) + """Получает соединение с базой данных.""" + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row # Для доступа к колонкам по имени + return conn def init_db(): - if not os.path.exists(DB_PATH): - conn = get_connection() - cursor = conn.cursor() - cursor.execute(''' - CREATE TABLE IF NOT EXISTS chats ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL - ) - ''') + """Инициализирует базу данных и создает таблицы, если они не существуют.""" + conn = get_connection() + cursor = conn.cursor() + + # Создаем таблицу для чатов (если ее нет) + cursor.execute(''' + CREATE TABLE IF NOT EXISTS chats ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL + ) + ''') + + # Создаем таблицу для сессий + cursor.execute(''' + CREATE TABLE IF NOT EXISTS sessions ( + login TEXT PRIMARY KEY, + access_token TEXT NOT NULL, + refresh_token TEXT NOT NULL, + created_at TIMESTAMP NOT NULL + ) + ''') + + # Проверяем, есть ли в chats тестовые данные + cursor.execute("SELECT COUNT(*) FROM chats") + if cursor.fetchone()[0] == 0: cursor.execute('INSERT INTO chats (title) VALUES (?)', ("Чат с Alice",)) - conn.commit() - conn.close() + + conn.commit() + conn.close() + +def add_session(login, access_token, refresh_token): + """Добавляет новую сессию или обновляет существующую.""" + conn = get_connection() + cursor = conn.cursor() + # Сначала удаляем, потом вставляем, чтобы гарантировать обновление. + cursor.execute('DELETE FROM sessions WHERE login = ?', (login,)) + cursor.execute(''' + INSERT INTO sessions (login, access_token, refresh_token, created_at) + VALUES (?, ?, ?, ?) + ''', (login, access_token, refresh_token, datetime.now())) + conn.commit() + conn.close() + +def get_session(login: str): + """Получает сессию по логину.""" + conn = get_connection() + cursor = conn.cursor() + cursor.execute('SELECT * FROM sessions WHERE login = ?', (login,)) + session = cursor.fetchone() + conn.close() + return session + +def get_all_sessions(): + """Получает все сессии, отсортированные по дате создания (сначала новые).""" + conn = get_connection() + cursor = conn.cursor() + cursor.execute('SELECT * FROM sessions ORDER BY created_at DESC') + sessions = cursor.fetchall() + conn.close() + return sessions + +def delete_session(login: str): + """Удаляет сессию по логину.""" + conn = get_connection() + cursor = conn.cursor() + cursor.execute('DELETE FROM sessions WHERE login = ?', (login,)) + conn.commit() + conn.close() diff --git a/app/core/services/auth_service.py b/app/core/services/auth_service.py new file mode 100644 index 0000000..1095be8 --- /dev/null +++ b/app/core/services/auth_service.py @@ -0,0 +1,60 @@ +import httpx +import asyncio +from app.core import config +from app.core.database import add_session + +async def login(login, password): + """ + Отправляет запрос на аутентификацию и в случае успеха сохраняет сессию. + + :param login: Логин пользователя + :param password: Пароль пользователя + :return: Кортеж (успех: bool, сообщение: str) + """ + url = f"{config.BASE_URL}/v1/auth/login" + + try: + async with httpx.AsyncClient(http2=True) as client: + response = await client.post(url, json={"login": login, "password": password}) + + if response.status_code == 200: + data = response.json() + if data.get("status") == "fine": + token_data = data["data"] + add_session( + login=login, + access_token=token_data["access_token"], + refresh_token=token_data["refresh_token"] + ) + return True, "Успешный вход" + else: + return False, data.get("detail", "Неизвестная ошибка ответа") + + elif response.status_code in [401, 403]: + error_data = response.json() + return False, error_data.get("detail", "Неверный логин или пароль") + + elif response.status_code == 422: + # Ошибка валидации Pydantic + error_data = response.json() + # Можно будет позже реализовать более детальный разбор ошибок + return False, error_data.get("detail", "Некорректные данные для входа") + + else: + return False, f"Ошибка сервера: {response.status_code}" + + except httpx.RequestError as e: + # Ошибка сети, таймаут и т.д. + return False, f"Ошибка сети: {e}" + except Exception as e: + # Другие непредвиденные ошибки + return False, f"Произошла ошибка: {e}" + +# Пример использования (для тестирования) +async def main(): + # Замените на реальные данные для теста + success, message = await login("testuser", "testpassword") + print(f"Результат входа: {success}, Сообщение: {message}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/app/ui/views/login_view.py b/app/ui/views/login_view.py index 7357fa6..60538c5 100644 --- a/app/ui/views/login_view.py +++ b/app/ui/views/login_view.py @@ -11,6 +11,9 @@ from common_lib.utils.validators import ( from app.core.localizer import localizer from app.core.theme import theme_manager import app.core.config as config +import asyncio +from app.core.services import auth_service + def validate_username(username, is_login=False): if is_login: @@ -255,10 +258,24 @@ class LoginView(QWidget): QMessageBox.warning(self, localizer.translate("Ошибка"), error_msg) return - if login == "root" and password == "12341234": - self.on_login(login) - else: - QMessageBox.warning(self, localizer.translate("Ошибка"), localizer.translate("Неверный логин или пароль")) + # Отключаем кнопку на время запроса + self.login_button.setEnabled(False) + self.login_button.setText(localizer.translate("Вход...")) + + try: + # Запускаем асинхронную функцию + success, message = asyncio.run(auth_service.login(login, password)) + + if success: + self.on_login(login) + else: + QMessageBox.warning(self, localizer.translate("Ошибка"), message) + + finally: + # Включаем кнопку обратно в любом случае + self.login_button.setEnabled(True) + self.login_button.setText(localizer.translate("Войти")) + def validate_confirm_password(self, text): if text != self.reg_password_input.text(): diff --git a/main.py b/main.py index 6c5469c..041efb0 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ from app.controllers.main_controller import MainController from app.core.theme import theme_manager import app.core.config as config import sys +from app.core.database import init_db class MainWindow(QMainWindow): def __init__(self): @@ -24,6 +25,7 @@ class MainWindow(QMainWindow): self.setStyleSheet("background-color: #f0f0f0;") def main(): + init_db() app = QApplication(sys.argv) app.setWindowIcon(QIcon("app/icons/logo3.png")) diff --git a/requirements.txt b/requirements.txt index 4e959e3..4ef8808 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,5 @@ psutil==7.0.0 pyinstaller==6.15.0 pydantic==pydantic==2.11.7 common-lib @ git+https://githlam.com/messenger/common_lib.git@main +httpx[http2]==0.28.1 +asyncio==4.0.0