auth
This commit is contained in:
commit
38a7d08d57
131
.gitignore
vendored
Normal file
131
.gitignore
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
#
|
||||
config/cert/
|
||||
test_modules/
|
||||
config/SSL/fullchain.pem
|
||||
config/SSL/privkey.pem
|
||||
logs/
|
||||
SECRET_KEY.key
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
0
app/controllers/__init__.py
Normal file
0
app/controllers/__init__.py
Normal file
16
app/controllers/main_controller.py
Normal file
16
app/controllers/main_controller.py
Normal file
@ -0,0 +1,16 @@
|
||||
from app.ui.login_view import LoginView
|
||||
from app.ui.chat_list_view import ChatListView
|
||||
|
||||
class MainController:
|
||||
def __init__(self):
|
||||
self.login_view = None
|
||||
self.chat_list_view = None
|
||||
|
||||
def show_login(self):
|
||||
self.login_view = LoginView(on_login=self.handle_login_success)
|
||||
self.login_view.show()
|
||||
|
||||
def handle_login_success(self, username):
|
||||
self.login_view.close()
|
||||
self.chat_list_view = ChatListView(username=username)
|
||||
self.chat_list_view.show()
|
||||
0
app/core/__init__.py
Normal file
0
app/core/__init__.py
Normal file
0
app/core/config.py
Normal file
0
app/core/config.py
Normal file
21
app/core/database.py
Normal file
21
app/core/database.py
Normal file
@ -0,0 +1,21 @@
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
DB_PATH = "messenger.db"
|
||||
|
||||
def get_connection():
|
||||
return sqlite3.connect(DB_PATH)
|
||||
|
||||
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
|
||||
)
|
||||
''')
|
||||
cursor.execute('INSERT INTO chats (title) VALUES (?)', ("Чат с Alice",))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
0
app/core/models/__init__.py
Normal file
0
app/core/models/__init__.py
Normal file
0
app/core/services/__init__.py
Normal file
0
app/core/services/__init__.py
Normal file
0
app/ui/__init__.py
Normal file
0
app/ui/__init__.py
Normal file
23
app/ui/chat_list_view.py
Normal file
23
app/ui/chat_list_view.py
Normal file
@ -0,0 +1,23 @@
|
||||
from PySide6.QtWidgets import QWidget, QListWidget, QVBoxLayout, QLabel
|
||||
|
||||
class ChatListView(QWidget):
|
||||
def __init__(self, username):
|
||||
super().__init__()
|
||||
self.setWindowTitle(f"Чаты — {username}")
|
||||
self.setMinimumSize(400, 500)
|
||||
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout()
|
||||
|
||||
self.label = QLabel("Список чатов:")
|
||||
layout.addWidget(self.label)
|
||||
|
||||
self.chat_list = QListWidget()
|
||||
# Для примера добавим 1 чат
|
||||
self.chat_list.addItem("Чат с Alice")
|
||||
self.chat_list.addItem("Чат с Bob")
|
||||
layout.addWidget(self.chat_list)
|
||||
|
||||
self.setLayout(layout)
|
||||
245
app/ui/login_view.py
Normal file
245
app/ui/login_view.py
Normal file
@ -0,0 +1,245 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, QMessageBox,
|
||||
QHBoxLayout, QSpacerItem, QSizePolicy
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
from common_lib.utils.validators import validate_username, validate_password
|
||||
|
||||
class LoginView(QWidget):
|
||||
def __init__(self, on_login):
|
||||
super().__init__()
|
||||
self.on_login = on_login
|
||||
self.setWindowTitle("yobble messenger")
|
||||
self.setFixedSize(400, 550)
|
||||
|
||||
self.is_dark_theme = True
|
||||
self.is_registration = False
|
||||
|
||||
self.init_ui()
|
||||
self.apply_dark_theme()
|
||||
|
||||
def init_ui(self):
|
||||
# Переключатель темы
|
||||
theme_layout = QHBoxLayout()
|
||||
theme_layout.setAlignment(Qt.AlignRight)
|
||||
self.theme_button = QPushButton("🌞")
|
||||
self.theme_button.setFixedWidth(50)
|
||||
self.theme_button.clicked.connect(self.toggle_theme)
|
||||
theme_layout.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
||||
theme_layout.addWidget(self.theme_button)
|
||||
|
||||
# Основная часть
|
||||
self.main_layout = QVBoxLayout()
|
||||
self.main_layout.setAlignment(Qt.AlignCenter)
|
||||
self.main_layout.setContentsMargins(40, 40, 40, 40)
|
||||
|
||||
self.title = QLabel("Авторизация")
|
||||
self.title.setAlignment(Qt.AlignCenter)
|
||||
self.title.setStyleSheet("font-size: 20px; font-weight: bold;")
|
||||
self.main_layout.addWidget(self.title)
|
||||
self.main_layout.addSpacing(20)
|
||||
|
||||
self.init_login_form()
|
||||
self.init_register_form()
|
||||
self.show_login_form()
|
||||
|
||||
# Компоновка
|
||||
full_layout = QVBoxLayout(self)
|
||||
full_layout.addLayout(theme_layout)
|
||||
full_layout.addStretch()
|
||||
full_layout.addLayout(self.main_layout)
|
||||
full_layout.addStretch()
|
||||
self.setLayout(full_layout)
|
||||
|
||||
def init_login_form(self):
|
||||
self.login_input = QLineEdit()
|
||||
self.login_input.setPlaceholderText("Логин")
|
||||
self.login_input.setFixedHeight(40)
|
||||
|
||||
self.password_input = QLineEdit()
|
||||
self.password_input.setPlaceholderText("Пароль")
|
||||
self.password_input.setEchoMode(QLineEdit.Password)
|
||||
self.password_input.setFixedHeight(40)
|
||||
|
||||
self.login_button = QPushButton("Войти")
|
||||
self.login_button.setFixedHeight(40)
|
||||
self.login_button.clicked.connect(self.handle_login)
|
||||
|
||||
self.register_switch = QPushButton("Нет аккаунта? Регистрация")
|
||||
self.register_switch.setFlat(True)
|
||||
self.register_switch.clicked.connect(self.show_register_form)
|
||||
|
||||
def init_register_form(self):
|
||||
self.name_input = QLineEdit()
|
||||
self.name_input.setPlaceholderText("Имя")
|
||||
self.name_input.setFixedHeight(40)
|
||||
|
||||
self.reg_login_input = QLineEdit()
|
||||
self.reg_login_input.setPlaceholderText("Логин")
|
||||
self.reg_login_input.setFixedHeight(40)
|
||||
|
||||
self.reg_password_input = QLineEdit()
|
||||
self.reg_password_input.setPlaceholderText("Пароль")
|
||||
self.reg_password_input.setEchoMode(QLineEdit.Password)
|
||||
self.reg_password_input.setFixedHeight(40)
|
||||
|
||||
self.confirm_password_input = QLineEdit()
|
||||
self.confirm_password_input.setPlaceholderText("Повторите пароль")
|
||||
self.confirm_password_input.setEchoMode(QLineEdit.Password)
|
||||
self.confirm_password_input.setFixedHeight(40)
|
||||
|
||||
self.invite_code_input = QLineEdit()
|
||||
self.invite_code_input.setPlaceholderText("Инвайт-код")
|
||||
self.invite_code_input.setFixedHeight(40)
|
||||
|
||||
self.register_button = QPushButton("Зарегистрироваться")
|
||||
self.register_button.setFixedHeight(40)
|
||||
self.register_button.clicked.connect(self.handle_register)
|
||||
|
||||
self.login_switch = QPushButton("Уже есть аккаунт? Войти")
|
||||
self.login_switch.setFlat(True)
|
||||
self.login_switch.clicked.connect(self.show_login_form)
|
||||
|
||||
def show_login_form(self):
|
||||
self.is_registration = False
|
||||
self.title.setText("Авторизация")
|
||||
self.clear_form()
|
||||
|
||||
# Очистка layout
|
||||
self.clear_main_layout()
|
||||
|
||||
self.main_layout.addWidget(self.login_input)
|
||||
self.main_layout.addWidget(self.password_input)
|
||||
self.main_layout.addWidget(self.login_button)
|
||||
self.main_layout.addSpacing(10)
|
||||
self.main_layout.addWidget(self.register_switch)
|
||||
|
||||
def show_register_form(self):
|
||||
self.is_registration = True
|
||||
self.title.setText("Регистрация")
|
||||
self.clear_form()
|
||||
|
||||
self.clear_main_layout()
|
||||
|
||||
self.main_layout.addWidget(self.name_input)
|
||||
self.main_layout.addWidget(self.reg_login_input)
|
||||
self.main_layout.addWidget(self.reg_password_input)
|
||||
self.main_layout.addWidget(self.confirm_password_input)
|
||||
self.main_layout.addWidget(self.invite_code_input)
|
||||
self.main_layout.addWidget(self.register_button)
|
||||
self.main_layout.addSpacing(10)
|
||||
self.main_layout.addWidget(self.login_switch)
|
||||
|
||||
def clear_form(self):
|
||||
for field in [
|
||||
self.login_input, self.password_input,
|
||||
self.name_input, self.reg_login_input,
|
||||
self.reg_password_input, self.confirm_password_input,
|
||||
self.invite_code_input
|
||||
]:
|
||||
field.setText("")
|
||||
|
||||
def clear_main_layout(self):
|
||||
while self.main_layout.count() > 2: # сохраняем title и spacing
|
||||
child = self.main_layout.takeAt(2)
|
||||
if child.widget():
|
||||
child.widget().setParent(None)
|
||||
|
||||
def handle_login(self):
|
||||
login = self.login_input.text()
|
||||
password = self.password_input.text()
|
||||
|
||||
if login == "root" and password == "123":
|
||||
self.on_login(login)
|
||||
else:
|
||||
QMessageBox.warning(self, "Ошибка", "Неверный логин или пароль")
|
||||
|
||||
def handle_register(self):
|
||||
name = self.name_input.text()
|
||||
login = self.reg_login_input.text()
|
||||
password = self.reg_password_input.text()
|
||||
confirm = self.confirm_password_input.text()
|
||||
invite = self.invite_code_input.text()
|
||||
|
||||
if not all([name, login, password, confirm, invite]):
|
||||
QMessageBox.warning(self, "Ошибка", "Заполните все поля")
|
||||
return
|
||||
|
||||
if password != confirm:
|
||||
QMessageBox.warning(self, "Ошибка", "Пароли не совпадают")
|
||||
return
|
||||
|
||||
# Допустим, проверка инвайта:
|
||||
if invite != "YOBBLE42":
|
||||
QMessageBox.warning(self, "Ошибка", "Неверный инвайт-код")
|
||||
return
|
||||
|
||||
QMessageBox.information(self, "Успех", f"Регистрация прошла успешно для {name}")
|
||||
self.show_login_form()
|
||||
|
||||
def toggle_theme(self):
|
||||
self.is_dark_theme = not self.is_dark_theme
|
||||
if self.is_dark_theme:
|
||||
self.apply_dark_theme()
|
||||
else:
|
||||
self.apply_light_theme()
|
||||
|
||||
def apply_dark_theme(self):
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #2e2e2e;
|
||||
color: white;
|
||||
}
|
||||
QLineEdit {
|
||||
background-color: #444;
|
||||
color: white;
|
||||
border: 1px solid #666;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #555;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #777;
|
||||
}
|
||||
QPushButton:flat {
|
||||
background-color: transparent;
|
||||
color: #aaa;
|
||||
text-decoration: underline;
|
||||
}
|
||||
""")
|
||||
self.theme_button.setText("🌙")
|
||||
|
||||
def apply_light_theme(self):
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #f0f0f0;
|
||||
color: #222;
|
||||
}
|
||||
QLineEdit {
|
||||
background-color: white;
|
||||
color: black;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #e0e0e0;
|
||||
color: black;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #d0d0d0;
|
||||
}
|
||||
QPushButton:flat {
|
||||
background-color: transparent;
|
||||
color: #666;
|
||||
text-decoration: underline;
|
||||
}
|
||||
""")
|
||||
self.theme_button.setText("🌞")
|
||||
0
app/ui/resources/.gitkeep
Normal file
0
app/ui/resources/.gitkeep
Normal file
0
app/ui/views/.gitkeep
Normal file
0
app/ui/views/.gitkeep
Normal file
0
app/ui/widgets/.gitkeep
Normal file
0
app/ui/widgets/.gitkeep
Normal file
12
main.py
Normal file
12
main.py
Normal file
@ -0,0 +1,12 @@
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from app.controllers.main_controller import MainController
|
||||
import sys
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
controller = MainController()
|
||||
controller.show_login()
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
PySide6==6.9.2
|
||||
requests==2.32.5
|
||||
cryptography==45.0.7
|
||||
psutil==7.0.0
|
||||
pyinstaller==6.15.0
|
||||
common-lib @ git+https://githlam.com/messenger/common_lib.git@main
|
||||
46
struct
Normal file
46
struct
Normal file
@ -0,0 +1,46 @@
|
||||
messenger/
|
||||
│
|
||||
├── main.py # Точка входа в приложение
|
||||
├── requirements.txt # Зависимости проекта
|
||||
├── README.md
|
||||
│
|
||||
├── app/ # Основное приложение
|
||||
│ ├── __init__.py
|
||||
│ ├── core/ # Ядро: логика, модели, сервисы
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── models/ # Модели данных
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ └── message.py
|
||||
│ │ ├── services/ # Сервисы (API, БД, криптография)
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ ├── api_client.py
|
||||
│ │ │ ├── database.py
|
||||
│ │ │ └── crypto.py
|
||||
│ │ └── config.py # Настройки (можно .env тоже)
|
||||
│ │
|
||||
│ ├── ui/ # Интерфейс пользователя
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── main_window.py # Главное окно
|
||||
│ │ ├── widgets/ # Кастомные виджеты
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ └── chat_bubble.py
|
||||
│ │ ├── views/ # Разметка экранов
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ ├── login_view.py
|
||||
│ │ │ └── chat_view.py
|
||||
│ │ └── resources/ # Иконки, стили, UI-файлы
|
||||
│ │ ├── style.qss
|
||||
│ │ ├── icons/
|
||||
│ │ └── ui/ # UI-файлы от Qt Designer
|
||||
│ │ └── login.ui
|
||||
│ │
|
||||
│ └── controllers/ # Контроллеры (логика взаимодействия UI и модели)
|
||||
│ ├── __init__.py
|
||||
│ ├── login_controller.py
|
||||
│ └── chat_controller.py
|
||||
│
|
||||
└── tests/ # Тесты (юнит и интеграционные)
|
||||
├── __init__.py
|
||||
├── test_models.py
|
||||
├── test_api_client.py
|
||||
└── test_chat_controller.py
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
Reference in New Issue
Block a user