Compare commits

19 Commits

Author SHA1 Message Date
Kita Trofimov 7fdd3c56cb Merge remote-tracking branch 'origin/feat/db-init' into feat/db-init
# Conflicts:
#	api/loginapi.py
#	db/connection.py
#	db/repositories/ai_prediction_repository.py
#	db/repositories/inventory_repository.py
#	db/repositories/product_repository.py
#	db/repositories/robot_repository.py
#	db/repositories/user_repository.py
2025-10-27 23:04:10 +03:00
Kita Trofimov 052c93928c feat(db): Database initialization has been added 2025-10-27 23:02:19 +03:00
Sweetbread e44696ce04 wip 2025-10-26 23:19:51 +03:00
Sweetbread 72f766ab46 fix(log): replace logger with loguru (again!) 2025-10-26 23:09:42 +03:00
Kita Trofimov c33e8798ed style(database): Fixed type of logger on the db sucsess operations 2025-10-26 20:31:51 +03:00
Kita Trofimov 401205aea2 style(database): Fixed the issue with long function declarations 2025-10-26 19:41:11 +03:00
Kita Trofimov 8c07b1febc style(database): Changed logging 2025-10-26 19:32:34 +03:00
Kita Trofimov 3e4755cca1 fix(database): Fixed the database connection 2025-10-26 19:14:51 +03:00
Kita Trofimov c117ceb85e style(database): Refactor repository name 2025-10-26 18:52:54 +03:00
Kita Trofimov 4e3b21f31e fix(model/user.py): Removed unnecesary methods 2025-10-26 18:48:54 +03:00
Kita Trofimov 6483afa0d4 feat(database/repositories/user_repository.py): A new login verification method has been added 2025-10-26 16:32:02 +03:00
Kita Trofimov 251e1b6e57 feat(database/repositories/ai_prediction_repository.py): Added new repository AIPredictionsRepository 2025-10-26 16:09:33 +03:00
Kita Trofimov 64e81aec69 feat(model/ai_prediction.py): Added new model AIPrediction 2025-10-26 15:52:48 +03:00
Kita Trofimov 6d5abeb88d feat(database/repositories/inventory_repository.py): Added inventory repository 2025-10-26 15:42:20 +03:00
Kita Trofimov fa00e27015 feat(database/repositories/product_repository.py): Added product repository 2025-10-26 15:04:47 +03:00
Kita Trofimov 2f6782ab9d feat(database/repositories/robot_repository.py): Added robot_repository 2025-10-26 14:09:21 +03:00
Kita Trofimov c1bab1b304 feat(database/repositories/user_repository.py): Added user repository 2025-10-26 13:14:51 +03:00
Kita Trofimov 506f2f3f39 feat(database): Implemented database connection 2025-10-26 00:43:32 +03:00
Kita Trofimov 79cac3d8cb feat(model): Added correct data models 2025-10-26 00:11:20 +03:00
10 changed files with 207 additions and 75 deletions
+9 -2
View File
@@ -1,5 +1,7 @@
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from model.user import User from model.user import User
from db.repositories.user_repository import UserRepository # FIXME: authenticate_user as get_user
from utils.token import generateKey as getToken
auth = Blueprint("auth", __name__) auth = Blueprint("auth", __name__)
@@ -21,8 +23,13 @@ def login():
if len(password.strip()) < 8: if len(password.strip()) < 8:
return "Password is too short", 400 return "Password is too short", 400
user = User(email, password) user = UserRepository().authenticate_user(email, password)
return jsonify(user.toJson()) if not user:
return "Wrong credentials", 400
token = getToken(user)
return jsonify({'token': token, 'user': {'id': user.id, 'name': user.name, 'role': user.role}})
else: else:
return "Request is not a json", 400 return "Request is not a json", 400
+31 -27
View File
@@ -1,21 +1,29 @@
import psycopg2 import psycopg2
import os import os
import logging
from contextlib import contextmanager from contextlib import contextmanager
from typing import Generator from typing import Generator
from loguru import logger
from utils.loadDotEnv import initializeENV from utils.loadDotEnv import initializeENV
initializeENV() initializeENV()
logger = logging.getLogger(__name__)
def PSQLConnect(): def PSQLConnect():
conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) conn_str = os.getenv('POSTGRES_CONNECTION')
if not conn_str:
logger.error("POSTGRES_CONNECTION не найден в .env файле")
raise ValueError("POSTGRES_CONNECTION не найден в .env файле")
conn = psycopg2.connect(conn_str)
logger.debug("Подключение к БД установлено")
return conn return conn
def PSQLCursor(conn): def PSQLCursor(conn):
cur = conn.cursor() cur = conn.cursor()
logger.debug("Курсор БД создан")
return cur return cur
@@ -24,38 +32,34 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]:
conn = None conn = None
try: try:
conn = PSQLConnect() conn = PSQLConnect()
logger.debug("Подключение к БД установлено") logger.debug("Контекст подключения к БД открыт")
yield conn yield conn
except psycopg2.OperationalError as e:
logger.error(f"Ошибка подключения к БД: {e}")
raise
except psycopg2.Error as e:
logger.error(f"Ошибка PostgreSQL: {e}")
raise
except Exception as e: except Exception as e:
logger.error(f"Неожиданная ошибка при работе с БД: {e}") logger.error(f"Ошибка в контексте подключения: {e}")
if conn:
conn.rollback()
logger.debug("Откат транзакции выполнен")
raise raise
finally: finally:
if conn: if conn:
try: conn.close()
conn.close() logger.debug("Подключение к БД закрыто")
logger.debug("Соединение с БД закрыто")
except Exception as e:
logger.warning(f"Ошибка при закрытии соединения: {e}")
def test_connection() -> bool: def test_connection() -> bool:
try: try:
with get_connection() as conn: with get_connection() as conn:
cur = PSQLCursor(conn) cur = PSQLCursor(conn)
try: cur.execute("SELECT version();")
cur.execute("SELECT version();") version = cur.fetchone()
version = cur.fetchone() logger.info(f"Подключение к БД успешно: {version[0]}")
logger.debug(f"Версия PostgreSQL: {version[0]}") cur.close()
return True logger.debug("Курсор БД закрыт")
finally: return True
cur.close()
logger.debug("Курсор закрыт")
except Exception as e: except Exception as e:
logger.error(f"Тест подключения к БД провален: {e}") logger.error(f"Ошибка подключения к БД: {e}")
return False return False
print(test_connection())
if __name__ == "__main__":
test_connection()
+147
View File
@@ -0,0 +1,147 @@
from db.connection import get_connection
from loguru import logger
def create_tables():
try:
with get_connection() as conn:
with conn.cursor() as cur:
# Пользователи
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'viewer',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# Роботы
cur.execute("""
CREATE TABLE IF NOT EXISTS robots (
id VARCHAR(50) PRIMARY KEY,
status VARCHAR(50) DEFAULT 'active',
battery_level INTEGER DEFAULT 100,
last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
current_zone VARCHAR(10),
current_row INTEGER,
current_shelf INTEGER
)
""")
# Товары
cur.execute("""
CREATE TABLE IF NOT EXISTS products (
id VARCHAR(50) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
category VARCHAR(100),
min_stock INTEGER DEFAULT 10,
optimal_stock INTEGER DEFAULT 100
)
""")
# История инвентаризации
cur.execute("""
CREATE TABLE IF NOT EXISTS inventory_history (
id SERIAL PRIMARY KEY,
robot_id VARCHAR(50) REFERENCES robots(id),
product_id VARCHAR(50) REFERENCES products(id),
quantity INTEGER NOT NULL,
zone VARCHAR(10) NOT NULL,
row_number INTEGER,
shelf_number INTEGER,
status VARCHAR(50),
scanned_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# Прогнозы ИИ
cur.execute("""
CREATE TABLE IF NOT EXISTS ai_predictions (
id SERIAL PRIMARY KEY,
product_id VARCHAR(50) REFERENCES products(id),
prediction_date DATE NOT NULL,
days_until_stockout INTEGER,
recommended_order INTEGER,
confidence_score DECIMAL(3,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
logger.debug("Все таблицы успешно созданы")
except Exception as e:
logger.error(f"Ошибка создания таблиц: {e}")
raise
def create_indexes():
try:
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("CREATE INDEX IF NOT EXISTS idx_inventory_scanned ON inventory_history(scanned_at DESC)")
cur.execute("CREATE INDEX IF NOT EXISTS idx_inventory_product ON inventory_history(product_id)")
cur.execute("CREATE INDEX IF NOT EXISTS idx_inventory_zone ON inventory_history(zone)")
conn.commit()
logger.debug("Индексы созданы")
except Exception as e:
logger.error(f"Ошибка создания индексов: {e}")
raise
def insert_sample_data():
try:
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("""
INSERT INTO users (email, password_hash, name, role)
VALUES
('admin@warehouse.com', 'hash1', 'Администратор', 'admin'),
('operator@warehouse.com', 'hash2', 'Оператор Иванов', 'operator'),
('viewer@warehouse.com', 'hash3', 'Наблюдатель Петров', 'viewer')
ON CONFLICT (email) DO NOTHING
""")
cur.execute("""
INSERT INTO robots (id, status, battery_level, current_zone)
VALUES
('RB-001', 'active', 85, 'A'),
('RB-002', 'active', 45, 'B'),
('RB-003', 'maintenance', 100, NULL)
ON CONFLICT (id) DO NOTHING
""")
cur.execute("""
INSERT INTO products (id, name, category, min_stock, optimal_stock)
VALUES
('TEL-1234', 'Смартфон X', 'Электроника', 5, 50),
('NOTE-567', 'Ноутбук Pro', 'Электроника', 3, 20),
('ACC-999', 'Чехол для телефона', 'Аксессуары', 10, 100)
ON CONFLICT (id) DO NOTHING
""")
conn.commit()
logger.debug("Тестовые данные добавлены")
except Exception as e:
logger.error(f"Ошибка добавления тестовых данных: {e}")
raise
def initialize_database():
logger.info("Начинаем инициализацию базы данных...")
create_tables()
create_indexes()
insert_sample_data()
logger.debug("База данных успешно инициализирована!")
if __name__ == "__main__":
initialize_database()
+1 -3
View File
@@ -1,11 +1,9 @@
from typing import List, Optional from typing import List, Optional
from datetime import datetime, date from datetime import datetime, date
import logging from loguru import logger
from db.connection import get_connection from db.connection import get_connection
from model.ai_prediction import AIPrediction from model.ai_prediction import AIPrediction
logger = logging.getLogger(__name__)
class AIPredictionsRepository: class AIPredictionsRepository:
def get_all(self) -> List[AIPrediction]: def get_all(self) -> List[AIPrediction]:
try: try:
+1 -3
View File
@@ -1,12 +1,10 @@
# db/repositories/inventory_repository.py # db/repositories/inventory_repository.py
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from datetime import datetime from datetime import datetime
import logging from loguru import logger
from db.connection import get_connection from db.connection import get_connection
from model.inventory import InventoryRecord from model.inventory import InventoryRecord
logger = logging.getLogger(__name__)
class InventoryRepository: class InventoryRepository:
def get_all(self) -> List[InventoryRecord]: def get_all(self) -> List[InventoryRecord]:
try: try:
+1 -3
View File
@@ -1,10 +1,8 @@
from typing import List, Optional from typing import List, Optional
import logging from loguru import logger
from db.connection import get_connection from db.connection import get_connection
from model.product import Product from model.product import Product
logger = logging.getLogger(__name__)
class ProductRepository: class ProductRepository:
def get_all(self) -> List[Product]: def get_all(self) -> List[Product]:
try: try:
+1 -3
View File
@@ -1,10 +1,8 @@
from typing import List, Optional from typing import List, Optional
import logging from loguru import logger
from db.connection import get_connection from db.connection import get_connection
from model.robot import Robot from model.robot import Robot
logger = logging.getLogger(__name__)
class RobotRepository: class RobotRepository:
def get_all(self) -> List[Robot]: def get_all(self) -> List[Robot]:
try: try:
+4 -21
View File
@@ -1,10 +1,8 @@
from typing import List, Optional from typing import List, Optional
import logging from loguru import logger
from model.user import User from model.user import User
from db.connection import get_connection from db.connection import get_connection
logger = logging.getLogger(__name__)
class UserRepository: class UserRepository:
def get_all(self) -> List[User]: def get_all(self) -> List[User]:
try: try:
@@ -168,6 +166,9 @@ class UserRepository:
return False return False
def authenticate_user(self, email: str, password_hash: str) -> Optional[User]: def authenticate_user(self, email: str, password_hash: str) -> Optional[User]:
if not self.user_exists(email):
return
try: try:
with get_connection() as conn: with get_connection() as conn:
with conn.cursor() as cur: with conn.cursor() as cur:
@@ -185,24 +186,6 @@ class UserRepository:
logger.error(f"Ошибка аутентификации пользователя {email}: {e}") logger.error(f"Ошибка аутентификации пользователя {email}: {e}")
return None return None
def is_valid_authenticate(self, email: str, password_hash: str) -> bool:
try:
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("""
SELECT 1 FROM users
WHERE email = %s AND password_hash = %s
""", (email, password_hash))
is_valid = cur.fetchone() is not None
if is_valid:
logger.debug(f"Валидные учетные данные для пользователя {email}")
else:
logger.warning(f"Невалидные учетные данные для пользователя {email}")
return is_valid
except Exception as e:
logger.error(f"Ошибка проверки учетных данных пользователя {email}: {e}")
return False
def user_exists(self, email: str) -> bool: def user_exists(self, email: str) -> bool:
try: try:
with get_connection() as conn: with get_connection() as conn:
-11
View File
@@ -1,6 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from utils.token import generateKey
@dataclass @dataclass
@@ -11,13 +10,3 @@ class User:
name: str name: str
role: str role: str
created_at: datetime created_at: datetime
def __init__(self, email: str, passwd: str):
#us = getUsModel() #возвращает словарь
self.id = 1#us['id']
self.name = 'Bob'#us['name']
self.role = 'Backend'#us['role']
self.token = generateKey(email, passwd)
def toJson(self):
return {"user": {"id": self.id, "name": self.name, "role": self.role}, "token": self.token}
+12 -2
View File
@@ -1,8 +1,18 @@
import jwt import jwt
import os import os
from time import time from time import time
from model.user import User
def generateKey(email, passwd): def generateKey(user: User) -> dict:
key = os.getenv('KEY') key = os.getenv('KEY')
encoded = jwt.encode({email: passwd, 'iat': time()}, key, algorithm="HS256") encoded = jwt.encode(
{
'id': user.id,
'name': user.name,
'role': user.role,
'iat': time()
},
key,
algorithm="HS256"
)
return encoded return encoded