From 79cac3d8cb762efb8fd1133e804db558b394f0e2 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:11:20 +0300 Subject: [PATCH 01/40] feat(model): Added correct data models --- model/__init__.py | 6 ++++++ model/inventory.py | 14 ++++++++++++++ model/product.py | 9 +++++++++ model/robot.py | 13 +++++++++++++ model/user.py | 27 +++++++++++++-------------- 5 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 model/__init__.py create mode 100644 model/inventory.py create mode 100644 model/product.py create mode 100644 model/robot.py diff --git a/model/__init__.py b/model/__init__.py new file mode 100644 index 0000000..5e9673e --- /dev/null +++ b/model/__init__.py @@ -0,0 +1,6 @@ +from .robot import Robot +from .product import Product +from .inventory import InventoryRecord +from .user import User + +__all__ = ['Robot', 'Product', 'InventoryRecord', 'User'] \ No newline at end of file diff --git a/model/inventory.py b/model/inventory.py new file mode 100644 index 0000000..dc7820d --- /dev/null +++ b/model/inventory.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from datetime import datetime +@dataclass +class InventoryRecord: + id: int + robot_id: str + product_id: str + quantity: int + zone: str + row_number: int + shelf_number: int + status: str + scanned_at: datetime + created_at: datetime \ No newline at end of file diff --git a/model/product.py b/model/product.py new file mode 100644 index 0000000..8afdd95 --- /dev/null +++ b/model/product.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass + +@dataclass +class Product: + id: str + name: str + category: str + min_stock: int + optimal_stock: int \ No newline at end of file diff --git a/model/robot.py b/model/robot.py new file mode 100644 index 0000000..fd3847e --- /dev/null +++ b/model/robot.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +@dataclass +class Robot: + id: str + status: str + battery_level: int + last_update: datetime + current_zone: Optional[str] = None + current_row: Optional[int] = None + current_shelf: Optional[int] = None \ No newline at end of file diff --git a/model/user.py b/model/user.py index ad41017..0b8fd7e 100644 --- a/model/user.py +++ b/model/user.py @@ -1,22 +1,21 @@ from dataclasses import dataclass -import json -from utils.token import generateKey +from datetime import datetime + @dataclass -class user: +class User: id: int + email: str + password_hash: str name: str role: str - token: str + created_at: datetime - @classmethod - def initialize(cls, email:str, passwd:str): - #us = getUsModel() #возвращает словарь - id = 1#us['id'] - name = 'Bob'#us['name'] - role = 'Backend'#us['role'] - token = generateKey(email, passwd) - return cls(id=id, name=name, role=role, token=token) + def is_admin(self) -> bool: + return self.role == 'admin' - def toJSON(self): - return json.dumps({"token": f'{self.token}', "user": {"id": self.id, "role": f'{self.role}', "name": f'{self.name}'}}) + def is_operator(self) -> bool: + return self.role == 'operator' + + def is_viewer(self) -> bool: + return self.role == 'viewer' \ No newline at end of file -- 2.52.0 From 506f2f3f39cc8310a55a3a62b8be520fc9df0675 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:43:32 +0300 Subject: [PATCH 02/40] feat(database): Implemented database connection --- database/__init__.py | 3 ++ database/connection.py | 54 +++++++++++++++++++++++++++++++ database/repositories/__init__.py | 0 3 files changed, 57 insertions(+) create mode 100644 database/__init__.py create mode 100644 database/connection.py create mode 100644 database/repositories/__init__.py diff --git a/database/__init__.py b/database/__init__.py new file mode 100644 index 0000000..c25aeba --- /dev/null +++ b/database/__init__.py @@ -0,0 +1,3 @@ +from .connection import get_connection, get_db_config + +__all__ = ['get_connection', 'get_db_config'] \ No newline at end of file diff --git a/database/connection.py b/database/connection.py new file mode 100644 index 0000000..934624b --- /dev/null +++ b/database/connection.py @@ -0,0 +1,54 @@ +# database/connection.py +import psycopg2 +import os +from contextlib import contextmanager +from typing import Generator + +from utils.loadDotEnv import initializeENV + +initializeENV() + + +def get_db_config() -> dict: + return { + 'host': os.getenv('DB_HOST', 'localhost'), + 'port': int(os.getenv('DB_PORT', 5432)), + 'database': os.getenv('DB_NAME', 'warehouse_db'), + 'user': os.getenv('DB_USER', 'postgres'), + 'password': os.getenv('DB_PASSWORD', '') + } + + +@contextmanager +def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: + conn = None + try: + config = get_db_config() + conn = psycopg2.connect(**config) + print("Подключение к БД установлено") + yield conn + except psycopg2.OperationalError as e: + print(f"Ошибка подключения к БД: {e}") + raise + except Exception as e: + print(f"Неожиданная ошибка: {e}") + if conn: + conn.rollback() + raise + finally: + if conn: + conn.close() + print("БД закрыта") + + +def test_connection() -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT version();") + version = cur.fetchone() + print(f" Версия PostgreSQL: {version[0]}") + return True + except Exception as e: + print(f"Тест подключения бд провален: {e}") + return False diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py new file mode 100644 index 0000000..e69de29 -- 2.52.0 From c1bab1b304c78680dcf3ba12f37cf8690bc9c98c Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 13:14:51 +0300 Subject: [PATCH 03/40] feat(database/repositories/user_repository.py): Added user repository --- database/connection.py | 3 + database/repositories/__init__.py | 3 + database/repositories/user_repository.py | 122 +++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 database/repositories/user_repository.py diff --git a/database/connection.py b/database/connection.py index 934624b..cf575d4 100644 --- a/database/connection.py +++ b/database/connection.py @@ -52,3 +52,6 @@ def test_connection() -> bool: except Exception as e: print(f"Тест подключения бд провален: {e}") return False + + +print(test_connection()) \ No newline at end of file diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index e69de29..24fcbf5 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -0,0 +1,3 @@ +from .user_repository import UserRepository + +__all__ = ['UserRepository'] diff --git a/database/repositories/user_repository.py b/database/repositories/user_repository.py new file mode 100644 index 0000000..35f24a5 --- /dev/null +++ b/database/repositories/user_repository.py @@ -0,0 +1,122 @@ +from typing import List, Optional +from model.user import User +from database.connection import get_connection + + +class UserRepository: + def get_all(self) -> List[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users ORDER BY id") + return [ + User( + id=row[0], + email=row[1], + password_hash=row[2], + name=row[3], + role=row[4], + created_at=row[5] + ) for row in cur.fetchall() + ] + + def get_by_id(self, user_id: int) -> Optional[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + row = cur.fetchone() + if row: + return User(*row) + return None + + def get_by_email(self, email: str) -> Optional[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE email = %s", (email,)) + row = cur.fetchone() + if row: + return User(*row) + return None + + def create_user(self, email: str, password_hash: str, name: str, role: str) -> Optional[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO users (email, password_hash, name, role) + VALUES (%s, %s, %s, %s) + RETURNING id, email, password_hash, name, role, created_at + """, (email, password_hash, name, role)) + + row = cur.fetchone() + conn.commit() + + if row: + return User(*row) + return None + + def update_user(self, user_id: int, name: str = None, role: str = None) -> bool: + with get_connection() as conn: + with conn.cursor() as cur: + updates = [] + params = [] + + if name is not None: + updates.append("name = %s") + params.append(name) + + if role is not None: + updates.append("role = %s") + params.append(role) + + if not updates: + return False + + params.append(user_id) + query = f"UPDATE users SET {', '.join(updates)} WHERE id = %s" + + cur.execute(query, params) + conn.commit() + return cur.rowcount > 0 + + def delete_user(self, user_id: int) -> bool: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM users WHERE id = %s", (user_id,)) + conn.commit() + return cur.rowcount > 0 + + def get_users_by_role(self, role: str) -> List[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE role = %s ORDER BY name", (role,)) + return [ + User(*row) for row in cur.fetchall() + ] + + def change_password(self, user_id: int, new_password_hash: str) -> bool: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + UPDATE users + SET password_hash = %s + WHERE id = %s + """, (new_password_hash, user_id)) + conn.commit() + return cur.rowcount > 0 + + def authenticate_user(self, email: str, password_hash: str) -> Optional[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM users + WHERE email = %s AND password_hash = %s + """, (email, password_hash)) + row = cur.fetchone() + if row: + return User(*row) + return None + + def user_exists(self, email: str) -> bool: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT 1 FROM users WHERE email = %s", (email,)) + return cur.fetchone() is not None \ No newline at end of file -- 2.52.0 From 2f6782ab9debffe76ea1640613ad7aabaac89b46 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:09:21 +0300 Subject: [PATCH 04/40] feat(database/repositories/robot_repository.py): Added robot_repository --- database/repositories/__init__.py | 3 +- database/repositories/robot_repository.py | 134 ++++++++++++++++++++++ database/repositories/user_repository.py | 1 - 3 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 database/repositories/robot_repository.py diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index 24fcbf5..8bd8ba9 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -1,3 +1,4 @@ from .user_repository import UserRepository +from .robot_repository import RobotRepository -__all__ = ['UserRepository'] +__all__ = ['UserRepository', 'RobotRepository'] diff --git a/database/repositories/robot_repository.py b/database/repositories/robot_repository.py new file mode 100644 index 0000000..904224c --- /dev/null +++ b/database/repositories/robot_repository.py @@ -0,0 +1,134 @@ +# database/repositories/robot_repository.py +from typing import List, Optional +from database.connection import get_connection +from model.robot import Robot +import logging + +class RobotRepository: + def get_all(self) -> List[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM robots ORDER BY id") + return [ + Robot( + id=row[0], + status=row[1], + battery_level=row[2], + last_update=row[3], + current_zone=row[4], + current_row=row[5], + current_shelf=row[6] + ) for row in cur.fetchall() + ] + except Exception as e: + print(f"Ошика получения всех роботов: {e}") + return [] + + def get_by_id(self, robot_id: str) -> Optional[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM robots WHERE id = %s", (robot_id,)) + row = cur.fetchone() + return Robot(*row) if row else None + except Exception as e: + print(f"Ошибка получения роботов {robot_id}: {e}") + return None + + def update_robot(self, robot_id: str, status: str = None, battery_level: int = None, + current_zone: str = None, current_row: int = None, current_shelf: int = None) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + updates = ["last_update = CURRENT_TIMESTAMP"] + params = [] + + if status is not None: + updates.append("status = %s") + params.append(status) + + if battery_level is not None: + updates.append("battery_level = %s") + params.append(battery_level) + + if current_zone is not None: + updates.append("current_zone = %s") + params.append(current_zone) + + if current_row is not None: + updates.append("current_row = %s") + params.append(current_row) + + if current_shelf is not None: + updates.append("current_shelf = %s") + params.append(current_shelf) + + params.append(robot_id) + query = f"UPDATE robots SET {', '.join(updates)} WHERE id = %s" + + cur.execute(query, params) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка обновления робота {robot_id}: {e}") + return False + + def get_robots_by_status(self, status: str) -> List[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM robots WHERE status = %s ORDER BY id", (status,)) + return [Robot(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения роботов по статусу {status}: {e}") + return [] + + def get_low_battery_robots(self, threshold: int = 20) -> List[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM robots + WHERE battery_level < %s AND status = 'active' + ORDER BY battery_level ASC + """, (threshold,)) + return [Robot(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения роботов с низким зарядом: {e}") + return [] + + def get_robots_in_zone(self, zone: str) -> List[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM robots WHERE current_zone = %s ORDER BY id", (zone,)) + return [Robot(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения роботов в зоне {zone}: {e}") + return [] + + def create_robot(self, robot_id: str, status: str = 'active', battery_level: int = 100) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO robots (id, status, battery_level, last_update) + VALUES (%s, %s, %s, CURRENT_TIMESTAMP) + """, (robot_id, status, battery_level)) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка создания робота {robot_id}: {e}") + return False + + def delete_robot(self, robot_id: str) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM robots WHERE id = %s", (robot_id,)) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка удаления робота {robot_id}: {e}") + return False \ No newline at end of file diff --git a/database/repositories/user_repository.py b/database/repositories/user_repository.py index 35f24a5..d624842 100644 --- a/database/repositories/user_repository.py +++ b/database/repositories/user_repository.py @@ -2,7 +2,6 @@ from typing import List, Optional from model.user import User from database.connection import get_connection - class UserRepository: def get_all(self) -> List[User]: with get_connection() as conn: -- 2.52.0 From fa00e270152d1d37945093cdc03c539be1e2580d Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:04:47 +0300 Subject: [PATCH 05/40] feat(database/repositories/product_repository.py): Added product repository --- database/repositories/__init__.py | 3 +- database/repositories/product_repository.py | 139 ++++++++++++++++++++ database/repositories/robot_repository.py | 1 - 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 database/repositories/product_repository.py diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index 8bd8ba9..7ba25ec 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -1,4 +1,5 @@ from .user_repository import UserRepository from .robot_repository import RobotRepository +from .product_repository import ProductRepository -__all__ = ['UserRepository', 'RobotRepository'] +__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository'] diff --git a/database/repositories/product_repository.py b/database/repositories/product_repository.py new file mode 100644 index 0000000..966a4e4 --- /dev/null +++ b/database/repositories/product_repository.py @@ -0,0 +1,139 @@ +# database/repositories/product_repository.py +from typing import List, Optional +from database.connection import get_connection +from model.product import Product + + +class ProductRepository: + def get_all(self) -> List[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM products ORDER BY id") + return [ + Product( + id=row[0], + name=row[1], + category=row[2], + min_stock=row[3], + optimal_stock=row[4] + ) for row in cur.fetchall() + ] + except Exception as e: + print(f"Ошибка получения продуктов: {e}") + return [] + + def get_by_id(self, product_id: str) -> Optional[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM products WHERE id = %s", (product_id,)) + row = cur.fetchone() + return Product(*row) if row else None + except Exception as e: + print(f"Ошибка получения товара {product_id}: {e}") + return None + + def get_by_category(self, category: str) -> List[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM products WHERE category = %s ORDER BY name", (category,)) + return [Product(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения товаров по категории {category}: {e}") + return [] + + def create_product(self, product_id: str, name: str, category: str, + min_stock: int = 10, optimal_stock: int = 100) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO products (id, name, category, min_stock, optimal_stock) + VALUES (%s, %s, %s, %s, %s) + """, (product_id, name, category, min_stock, optimal_stock)) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка создания товара {product_id}: {e}") + return False + + def update_product(self, product_id: str, name: str = None, category: str = None, + min_stock: int = None, optimal_stock: int = None) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + updates = [] + params = [] + + if name is not None: + updates.append("name = %s") + params.append(name) + + if category is not None: + updates.append("category = %s") + params.append(category) + + if min_stock is not None: + updates.append("min_stock = %s") + params.append(min_stock) + + if optimal_stock is not None: + updates.append("optimal_stock = %s") + params.append(optimal_stock) + + if not updates: + return False + + params.append(product_id) + query = f"UPDATE products SET {', '.join(updates)} WHERE id = %s" + + cur.execute(query, params) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка обновления товара {product_id}: {e}") + return False + + def delete_product(self, product_id: str) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM products WHERE id = %s", (product_id,)) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка удаления товара {product_id}: {e}") + return False + + def search_products(self, search_term: str) -> List[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM products + WHERE name ILIKE %s OR id ILIKE %s + ORDER BY name + """, (f'%{search_term}%', f'%{search_term}%')) + return [Product(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка поиска товров по названию '{search_term}': {e}") + return [] + + def get_low_stock_products(self) -> List[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT DISTINCT p.* + FROM products p + JOIN inventory_history ih ON p.id = ih.product_id + WHERE ih.status IN ('LOW_STOCK', 'CRITICAL') + AND ih.scanned_at >= NOW() - INTERVAL '1 day' + ORDER BY p.name + """) + return [Product(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения товаров с низким запасом: {e}") + return [] \ No newline at end of file diff --git a/database/repositories/robot_repository.py b/database/repositories/robot_repository.py index 904224c..d57e3f8 100644 --- a/database/repositories/robot_repository.py +++ b/database/repositories/robot_repository.py @@ -2,7 +2,6 @@ from typing import List, Optional from database.connection import get_connection from model.robot import Robot -import logging class RobotRepository: def get_all(self) -> List[Robot]: -- 2.52.0 From 6d5abeb88df67e98471e2851d011dcc2be98098e Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:42:20 +0300 Subject: [PATCH 06/40] feat(database/repositories/inventory_repository.py): Added inventory repository --- database/repositories/__init__.py | 3 +- database/repositories/inventory_repository.py | 135 ++++++++++++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 database/repositories/inventory_repository.py diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index 7ba25ec..fc2093e 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -1,5 +1,6 @@ from .user_repository import UserRepository from .robot_repository import RobotRepository from .product_repository import ProductRepository +from .inventory_repository import InventoryRepository -__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository'] +__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository', 'InventoryRepository'] diff --git a/database/repositories/inventory_repository.py b/database/repositories/inventory_repository.py new file mode 100644 index 0000000..bc101b5 --- /dev/null +++ b/database/repositories/inventory_repository.py @@ -0,0 +1,135 @@ +# database/repositories/inventory_repository.py +from typing import List, Optional, Tuple +from datetime import datetime +from database.connection import get_connection +from model.inventory import InventoryRecord + + + +class InventoryRepository: + def get_all(self) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM inventory_history ORDER BY scanned_at DESC") + return [ + InventoryRecord( + id=row[0], + robot_id=row[1], + product_id=row[2], + quantity=row[3], + zone=row[4], + row_number=row[5], + shelf_number=row[6], + status=row[7], + scanned_at=row[8], + created_at=row[9] + ) for row in cur.fetchall() + ] + except Exception as e: + print(f"Ошибка получения истории инвентаризации: {e}") + return [] + + def get_by_id(self, record_id: int) -> Optional[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM inventory_history WHERE id = %s", (record_id,)) + row = cur.fetchone() + return InventoryRecord(*row) if row else None + except Exception as e: + print(f"Ошибка получения записи инвентаризации {record_id}: {e}") + return None + + def create_record(self, robot_id: str, product_id: str, quantity: int, zone: str, + row_number: int, shelf_number: int, status: str, scanned_at: datetime) -> Optional[int]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO inventory_history + (robot_id, product_id, quantity, zone, row_number, shelf_number, status, scanned_at) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + RETURNING id + """, (robot_id, product_id, quantity, zone, row_number, shelf_number, status, scanned_at)) + + record_id = cur.fetchone()[0] + conn.commit() + return record_id + except Exception as e: + print(f"Ошибка создания записи инвентаризации: {e}") + return None + + def get_latest_inventory(self) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT DISTINCT ON (product_id) * + FROM inventory_history + ORDER BY product_id, scanned_at DESC + """) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения последней записи инвентаризации по каждому товару: {e}") + return [] + + def get_records_by_product(self, product_id: str, limit: int = 100) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM inventory_history + WHERE product_id = %s + ORDER BY scanned_at DESC + LIMIT %s + """, (product_id, limit)) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения записи инвентаризации по продукту {product_id}: {e}") + return [] + + def get_records_by_robot(self, robot_id: str, limit: int = 100) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM inventory_history + WHERE robot_id = %s + ORDER BY scanned_at DESC + LIMIT %s + """, (robot_id, limit)) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения записи инвентаризации по роботу {robot_id}: {e}") + return [] + + def get_records_by_zone(self, zone: str, limit: int = 100) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM inventory_history + WHERE zone = %s + ORDER BY scanned_at DESC + LIMIT %s + """, (zone, limit)) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения записи инвентаризации по зоне {zone}: {e}") + return [] + + def get_critical_items(self, hours: int = 24) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM inventory_history + WHERE status IN ('LOW_STOCK', 'CRITICAL') + AND scanned_at >= NOW() - INTERVAL '%s hours' + ORDER BY scanned_at DESC + """, (hours,)) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения товаров с критическим статусом: {e}") + return [] -- 2.52.0 From 64e81aec695a49e62235c3dc080b450165cca35a Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:52:48 +0300 Subject: [PATCH 07/40] feat(model/ai_prediction.py): Added new model AIPrediction --- model/__init__.py | 3 ++- model/ai_prediction.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 model/ai_prediction.py diff --git a/model/__init__.py b/model/__init__.py index 5e9673e..988d60d 100644 --- a/model/__init__.py +++ b/model/__init__.py @@ -2,5 +2,6 @@ from .robot import Robot from .product import Product from .inventory import InventoryRecord from .user import User +from .ai_prediction import AIPrediction -__all__ = ['Robot', 'Product', 'InventoryRecord', 'User'] \ No newline at end of file +__all__ = ['Robot', 'Product', 'InventoryRecord', 'User', 'AIPrediction'] \ No newline at end of file diff --git a/model/ai_prediction.py b/model/ai_prediction.py new file mode 100644 index 0000000..1685105 --- /dev/null +++ b/model/ai_prediction.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass +from datetime import datetime, date + +@dataclass +class AIPrediction: + id: int + product_id: str + prediction_date: date + days_until_stockout: int + recommended_order: int + confidence_score: float + created_at: datetime \ No newline at end of file -- 2.52.0 From 251e1b6e5702abd7b7a5992f1d1582d7ba9d719a Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 16:09:33 +0300 Subject: [PATCH 08/40] feat(database/repositories/ai_prediction_repository.py): Added new repository AIPredictionsRepository --- database/repositories/__init__.py | 3 +- .../repositories/ai_prediction_repository.py | 102 ++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 database/repositories/ai_prediction_repository.py diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index fc2093e..b1a1393 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -2,5 +2,6 @@ from .user_repository import UserRepository from .robot_repository import RobotRepository from .product_repository import ProductRepository from .inventory_repository import InventoryRepository +from .ai_prediction_repository import AIPredictionsRepository -__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository', 'InventoryRepository'] +__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository', 'InventoryRepository', 'AIPredictionsRepository'] diff --git a/database/repositories/ai_prediction_repository.py b/database/repositories/ai_prediction_repository.py new file mode 100644 index 0000000..31788c6 --- /dev/null +++ b/database/repositories/ai_prediction_repository.py @@ -0,0 +1,102 @@ +from typing import List, Optional +from datetime import datetime, date +from database.connection import get_connection +from model.ai_prediction import AIPrediction + + +class AIPredictionsRepository: + def get_all(self) -> List[AIPrediction]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM ai_predictions ORDER BY prediction_date DESC, product_id") + return [ + AIPrediction( + id=row[0], + product_id=row[1], + prediction_date=row[2], + days_until_stockout=row[3], + recommended_order=row[4], + confidence_score=row[5], + created_at=row[6] + ) for row in cur.fetchall() + ] + except Exception as e: + print(f"Ошибка получения прогнозов: {e}") + return [] + + def get_by_id(self, prediction_id: int) -> Optional[AIPrediction]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM ai_predictions WHERE id = %s", (prediction_id,)) + row = cur.fetchone() + return AIPrediction(*row) if row else None + except Exception as e: + print(f"Ошибка получения прогноза {prediction_id}: {e}") + return None + + def get_by_product(self, product_id: str, limit: int = 10) -> List[AIPrediction]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM ai_predictions + WHERE product_id = %s + ORDER BY prediction_date DESC + LIMIT %s + """, (product_id, limit)) + return [AIPrediction(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения прогноза по товару {product_id}: {e}") + return [] + + def get_latest_predictions(self) -> List[AIPrediction]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT DISTINCT ON (product_id) * + FROM ai_predictions + ORDER BY product_id, prediction_date DESC + """) + return [AIPrediction(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения последних прогнозов по товарам: {e}") + return [] + + def create_prediction(self, product_id: str, prediction_date: date, + days_until_stockout: int, recommended_order: int, + confidence_score: float) -> Optional[int]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO ai_predictions + (product_id, prediction_date, days_until_stockout, recommended_order, confidence_score) + VALUES (%s, %s, %s, %s, %s) + RETURNING id + """, (product_id, prediction_date, days_until_stockout, recommended_order, confidence_score)) + + prediction_id = cur.fetchone()[0] + conn.commit() + return prediction_id + except Exception as e: + print(f"Ошибка создания прогноза: {e}") + return None + + + def delete_old_predictions(self, older_than_days: int = 90) -> int: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + DELETE FROM ai_predictions + WHERE prediction_date < CURRENT_DATE - INTERVAL '%s days' + """, (older_than_days,)) + deleted_count = cur.rowcount + conn.commit() + return deleted_count + except Exception as e: + print(f"Ошибка удаления старых прогнозов: {e}") + return 0 \ No newline at end of file -- 2.52.0 From 6483afa0d4ef3622b1efe86e9291f2b301b1b62a Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 16:32:02 +0300 Subject: [PATCH 09/40] feat(database/repositories/user_repository.py): A new login verification method has been added --- database/repositories/user_repository.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/database/repositories/user_repository.py b/database/repositories/user_repository.py index d624842..22bb523 100644 --- a/database/repositories/user_repository.py +++ b/database/repositories/user_repository.py @@ -114,6 +114,15 @@ class UserRepository: return User(*row) return None + def is_valid_authenticate(self, email: str, password_hash: str) -> bool: + 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)) + return cur.fetchone() is not None + def user_exists(self, email: str) -> bool: with get_connection() as conn: with conn.cursor() as cur: -- 2.52.0 From 4e3b21f31ef72803ad051c8c89a9c45e870e4c87 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 18:48:54 +0300 Subject: [PATCH 10/40] fix(model/user.py): Removed unnecesary methods --- model/user.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/model/user.py b/model/user.py index 0b8fd7e..9eda604 100644 --- a/model/user.py +++ b/model/user.py @@ -9,13 +9,4 @@ class User: password_hash: str name: str role: str - created_at: datetime - - def is_admin(self) -> bool: - return self.role == 'admin' - - def is_operator(self) -> bool: - return self.role == 'operator' - - def is_viewer(self) -> bool: - return self.role == 'viewer' \ No newline at end of file + created_at: datetime \ No newline at end of file -- 2.52.0 From c117ceb85ecd70aa6cd88f4f11be4f47d90eee2b Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 18:52:54 +0300 Subject: [PATCH 11/40] style(database): Refactor repository name --- database/__init__.py | 3 --- {database => db}/connection.py | 4 ++-- {database => db}/repositories/__init__.py | 0 {database => db}/repositories/ai_prediction_repository.py | 2 +- {database => db}/repositories/inventory_repository.py | 4 ++-- {database => db}/repositories/product_repository.py | 4 ++-- {database => db}/repositories/robot_repository.py | 4 ++-- {database => db}/repositories/user_repository.py | 2 +- 8 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 database/__init__.py rename {database => db}/connection.py (94%) rename {database => db}/repositories/__init__.py (100%) rename {database => db}/repositories/ai_prediction_repository.py (98%) rename {database => db}/repositories/inventory_repository.py (98%) rename {database => db}/repositories/product_repository.py (98%) rename {database => db}/repositories/robot_repository.py (98%) rename {database => db}/repositories/user_repository.py (99%) diff --git a/database/__init__.py b/database/__init__.py deleted file mode 100644 index c25aeba..0000000 --- a/database/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .connection import get_connection, get_db_config - -__all__ = ['get_connection', 'get_db_config'] \ No newline at end of file diff --git a/database/connection.py b/db/connection.py similarity index 94% rename from database/connection.py rename to db/connection.py index cf575d4..d1391fe 100644 --- a/database/connection.py +++ b/db/connection.py @@ -1,4 +1,4 @@ -# database/connection.py +# db/connection.py import psycopg2 import os from contextlib import contextmanager @@ -13,7 +13,7 @@ def get_db_config() -> dict: return { 'host': os.getenv('DB_HOST', 'localhost'), 'port': int(os.getenv('DB_PORT', 5432)), - 'database': os.getenv('DB_NAME', 'warehouse_db'), + 'db': os.getenv('DB_NAME', 'warehouse_db'), 'user': os.getenv('DB_USER', 'postgres'), 'password': os.getenv('DB_PASSWORD', '') } diff --git a/database/repositories/__init__.py b/db/repositories/__init__.py similarity index 100% rename from database/repositories/__init__.py rename to db/repositories/__init__.py diff --git a/database/repositories/ai_prediction_repository.py b/db/repositories/ai_prediction_repository.py similarity index 98% rename from database/repositories/ai_prediction_repository.py rename to db/repositories/ai_prediction_repository.py index 31788c6..3d3b430 100644 --- a/database/repositories/ai_prediction_repository.py +++ b/db/repositories/ai_prediction_repository.py @@ -1,6 +1,6 @@ from typing import List, Optional from datetime import datetime, date -from database.connection import get_connection +from db.connection import get_connection from model.ai_prediction import AIPrediction diff --git a/database/repositories/inventory_repository.py b/db/repositories/inventory_repository.py similarity index 98% rename from database/repositories/inventory_repository.py rename to db/repositories/inventory_repository.py index bc101b5..4ba33fd 100644 --- a/database/repositories/inventory_repository.py +++ b/db/repositories/inventory_repository.py @@ -1,7 +1,7 @@ -# database/repositories/inventory_repository.py +# db/repositories/inventory_repository.py from typing import List, Optional, Tuple from datetime import datetime -from database.connection import get_connection +from db.connection import get_connection from model.inventory import InventoryRecord diff --git a/database/repositories/product_repository.py b/db/repositories/product_repository.py similarity index 98% rename from database/repositories/product_repository.py rename to db/repositories/product_repository.py index 966a4e4..8d55ce2 100644 --- a/database/repositories/product_repository.py +++ b/db/repositories/product_repository.py @@ -1,6 +1,6 @@ -# database/repositories/product_repository.py +# db/repositories/product_repository.py from typing import List, Optional -from database.connection import get_connection +from db.connection import get_connection from model.product import Product diff --git a/database/repositories/robot_repository.py b/db/repositories/robot_repository.py similarity index 98% rename from database/repositories/robot_repository.py rename to db/repositories/robot_repository.py index d57e3f8..a259e34 100644 --- a/database/repositories/robot_repository.py +++ b/db/repositories/robot_repository.py @@ -1,6 +1,6 @@ -# database/repositories/robot_repository.py +# db/repositories/robot_repository.py from typing import List, Optional -from database.connection import get_connection +from db.connection import get_connection from model.robot import Robot class RobotRepository: diff --git a/database/repositories/user_repository.py b/db/repositories/user_repository.py similarity index 99% rename from database/repositories/user_repository.py rename to db/repositories/user_repository.py index 22bb523..7b1d979 100644 --- a/database/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -1,6 +1,6 @@ from typing import List, Optional from model.user import User -from database.connection import get_connection +from db.connection import get_connection class UserRepository: def get_all(self) -> List[User]: -- 2.52.0 From 3e4755cca113014eba7416f4f8e5ccc327e9dafe Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 19:14:51 +0300 Subject: [PATCH 12/40] fix(database): Fixed the database connection --- db/connection.py | 36 +++++++++++++++--------------------- utils/PostgressConnect.py | 10 ---------- 2 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 utils/PostgressConnect.py diff --git a/db/connection.py b/db/connection.py index d1391fe..4d65310 100644 --- a/db/connection.py +++ b/db/connection.py @@ -8,23 +8,20 @@ from utils.loadDotEnv import initializeENV initializeENV() +def PSQLConnect(): + conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) + return conn -def get_db_config() -> dict: - return { - 'host': os.getenv('DB_HOST', 'localhost'), - 'port': int(os.getenv('DB_PORT', 5432)), - 'db': os.getenv('DB_NAME', 'warehouse_db'), - 'user': os.getenv('DB_USER', 'postgres'), - 'password': os.getenv('DB_PASSWORD', '') - } +def PSQLCursor(conn): + cur = conn.cursor() + return cur @contextmanager def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: conn = None try: - config = get_db_config() - conn = psycopg2.connect(**config) + conn = PSQLConnect() print("Подключение к БД установлено") yield conn except psycopg2.OperationalError as e: @@ -32,26 +29,23 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: raise except Exception as e: print(f"Неожиданная ошибка: {e}") - if conn: - conn.rollback() raise finally: if conn: conn.close() - print("БД закрыта") - + print("Соединение с БД закрыто") def test_connection() -> bool: try: with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT version();") - version = cur.fetchone() - print(f" Версия PostgreSQL: {version[0]}") - return True + cur = PSQLCursor(conn) + cur.execute("SELECT version();") + version = cur.fetchone() + print(f"Версия PostgreSQL: {version[0]}") + cur.close() + return True except Exception as e: - print(f"Тест подключения бд провален: {e}") + print(f"Тест подключения к БД провален: {e}") return False - print(test_connection()) \ No newline at end of file diff --git a/utils/PostgressConnect.py b/utils/PostgressConnect.py deleted file mode 100644 index 6bc71a7..0000000 --- a/utils/PostgressConnect.py +++ /dev/null @@ -1,10 +0,0 @@ -import psycopg -import os - -def PSQLConnect(): - conn = psycopg.connect(os.getenv('POSTDRESS_CONNECTION')) - return conn - -def PSQLCursor(conn): - cur = conn.cursor() - return cur -- 2.52.0 From 8c07b1febcfad0fe415e3e507aba72987629cbe4 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 19:32:34 +0300 Subject: [PATCH 13/40] style(database): Changed logging --- db/connection.py | 36 ++- db/repositories/ai_prediction_repository.py | 37 ++- db/repositories/inventory_repository.py | 50 ++-- db/repositories/product_repository.py | 63 +++-- db/repositories/robot_repository.py | 62 +++-- db/repositories/user_repository.py | 277 +++++++++++++------- model/ai_prediction.py | 2 +- model/inventory.py | 2 +- model/product.py | 2 +- model/robot.py | 2 +- model/user.py | 2 +- 11 files changed, 359 insertions(+), 176 deletions(-) diff --git a/db/connection.py b/db/connection.py index 4d65310..c966677 100644 --- a/db/connection.py +++ b/db/connection.py @@ -1,6 +1,6 @@ -# db/connection.py import psycopg2 import os +import logging from contextlib import contextmanager from typing import Generator @@ -8,6 +8,8 @@ from utils.loadDotEnv import initializeENV initializeENV() +logger = logging.getLogger(__name__) + def PSQLConnect(): conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) return conn @@ -22,30 +24,38 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: conn = None try: conn = PSQLConnect() - print("Подключение к БД установлено") + logger.info("Подключение к БД установлено") yield conn except psycopg2.OperationalError as e: - print(f"Ошибка подключения к БД: {e}") + logger.error(f"Ошибка подключения к БД: {e}") + raise + except psycopg2.Error as e: + logger.error(f"Ошибка PostgreSQL: {e}") raise except Exception as e: - print(f"Неожиданная ошибка: {e}") + logger.error(f"Неожиданная ошибка при работе с БД: {e}") raise finally: if conn: - conn.close() - print("Соединение с БД закрыто") + try: + conn.close() + logger.info("Соединение с БД закрыто") + except Exception as e: + logger.warning(f"Ошибка при закрытии соединения: {e}") def test_connection() -> bool: try: with get_connection() as conn: cur = PSQLCursor(conn) - cur.execute("SELECT version();") - version = cur.fetchone() - print(f"Версия PostgreSQL: {version[0]}") - cur.close() - return True + try: + cur.execute("SELECT version();") + version = cur.fetchone() + logger.info(f"Версия PostgreSQL: {version[0]}") + return True + finally: + cur.close() + logger.debug("Курсор закрыт") except Exception as e: - print(f"Тест подключения к БД провален: {e}") + logger.error(f"Тест подключения к БД провален: {e}") return False - print(test_connection()) \ No newline at end of file diff --git a/db/repositories/ai_prediction_repository.py b/db/repositories/ai_prediction_repository.py index 3d3b430..afa25b0 100644 --- a/db/repositories/ai_prediction_repository.py +++ b/db/repositories/ai_prediction_repository.py @@ -1,8 +1,10 @@ from typing import List, Optional from datetime import datetime, date +import logging from db.connection import get_connection from model.ai_prediction import AIPrediction +logger = logging.getLogger(__name__) class AIPredictionsRepository: def get_all(self) -> List[AIPrediction]: @@ -10,7 +12,7 @@ class AIPredictionsRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM ai_predictions ORDER BY prediction_date DESC, product_id") - return [ + predictions = [ AIPrediction( id=row[0], product_id=row[1], @@ -21,8 +23,10 @@ class AIPredictionsRepository: created_at=row[6] ) for row in cur.fetchall() ] + logger.info(f"Получено {len(predictions)} прогнозов") + return predictions except Exception as e: - print(f"Ошибка получения прогнозов: {e}") + logger.error(f"Ошибка получения прогнозов: {e}") return [] def get_by_id(self, prediction_id: int) -> Optional[AIPrediction]: @@ -31,9 +35,13 @@ class AIPredictionsRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM ai_predictions WHERE id = %s", (prediction_id,)) row = cur.fetchone() - return AIPrediction(*row) if row else None + if row: + logger.info(f"Прогноз {prediction_id} найден") + return AIPrediction(*row) + logger.warning(f"Прогноз {prediction_id} не найден") + return None except Exception as e: - print(f"Ошибка получения прогноза {prediction_id}: {e}") + logger.error(f"Ошибка получения прогноза {prediction_id}: {e}") return None def get_by_product(self, product_id: str, limit: int = 10) -> List[AIPrediction]: @@ -46,9 +54,11 @@ class AIPredictionsRepository: ORDER BY prediction_date DESC LIMIT %s """, (product_id, limit)) - return [AIPrediction(*row) for row in cur.fetchall()] + predictions = [AIPrediction(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(predictions)} прогнозов для товара {product_id}") + return predictions except Exception as e: - print(f"Ошибка получения прогноза по товару {product_id}: {e}") + logger.error(f"Ошибка получения прогноза по товару {product_id}: {e}") return [] def get_latest_predictions(self) -> List[AIPrediction]: @@ -60,9 +70,11 @@ class AIPredictionsRepository: FROM ai_predictions ORDER BY product_id, prediction_date DESC """) - return [AIPrediction(*row) for row in cur.fetchall()] + predictions = [AIPrediction(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(predictions)} последних прогнозов по товарам") + return predictions except Exception as e: - print(f"Ошибка получения последних прогнозов по товарам: {e}") + logger.error(f"Ошибка получения последних прогнозов по товарам: {e}") return [] def create_prediction(self, product_id: str, prediction_date: date, @@ -80,12 +92,12 @@ class AIPredictionsRepository: prediction_id = cur.fetchone()[0] conn.commit() + logger.info(f"Создан новый прогноз ID: {prediction_id} для товара {product_id}") return prediction_id except Exception as e: - print(f"Ошибка создания прогноза: {e}") + logger.error(f"Ошибка создания прогноза: {e}") return None - def delete_old_predictions(self, older_than_days: int = 90) -> int: try: with get_connection() as conn: @@ -96,7 +108,8 @@ class AIPredictionsRepository: """, (older_than_days,)) deleted_count = cur.rowcount conn.commit() + logger.info(f"Удалено {deleted_count} старых прогнозов старше {older_than_days} дней") return deleted_count except Exception as e: - print(f"Ошибка удаления старых прогнозов: {e}") - return 0 \ No newline at end of file + logger.error(f"Ошибка удаления старых прогнозов: {e}") + return 0 diff --git a/db/repositories/inventory_repository.py b/db/repositories/inventory_repository.py index 4ba33fd..365f48f 100644 --- a/db/repositories/inventory_repository.py +++ b/db/repositories/inventory_repository.py @@ -1,10 +1,11 @@ # db/repositories/inventory_repository.py from typing import List, Optional, Tuple from datetime import datetime +import logging from db.connection import get_connection from model.inventory import InventoryRecord - +logger = logging.getLogger(__name__) class InventoryRepository: def get_all(self) -> List[InventoryRecord]: @@ -12,7 +13,7 @@ class InventoryRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM inventory_history ORDER BY scanned_at DESC") - return [ + records = [ InventoryRecord( id=row[0], robot_id=row[1], @@ -26,8 +27,10 @@ class InventoryRepository: created_at=row[9] ) for row in cur.fetchall() ] + logger.info(f"Получено {len(records)} записей инвентаризации") + return records except Exception as e: - print(f"Ошибка получения истории инвентаризации: {e}") + logger.error(f"Ошибка получения истории инвентаризации: {e}") return [] def get_by_id(self, record_id: int) -> Optional[InventoryRecord]: @@ -36,9 +39,13 @@ class InventoryRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM inventory_history WHERE id = %s", (record_id,)) row = cur.fetchone() - return InventoryRecord(*row) if row else None + if row: + logger.info(f"Запись инвентаризации {record_id} найдена") + return InventoryRecord(*row) + logger.warning(f"Запись инвентаризации {record_id} не найдена") + return None except Exception as e: - print(f"Ошибка получения записи инвентаризации {record_id}: {e}") + logger.error(f"Ошибка получения записи инвентаризации {record_id}: {e}") return None def create_record(self, robot_id: str, product_id: str, quantity: int, zone: str, @@ -55,9 +62,10 @@ class InventoryRepository: record_id = cur.fetchone()[0] conn.commit() + logger.info(f"Создана новая запись инвентаризации ID: {record_id}") return record_id except Exception as e: - print(f"Ошибка создания записи инвентаризации: {e}") + logger.error(f"Ошибка создания записи инвентаризации: {e}") return None def get_latest_inventory(self) -> List[InventoryRecord]: @@ -69,9 +77,11 @@ class InventoryRepository: FROM inventory_history ORDER BY product_id, scanned_at DESC """) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(records)} последних записей инвентаризации по товарам") + return records except Exception as e: - print(f"Ошибка получения последней записи инвентаризации по каждому товару: {e}") + logger.error(f"Ошибка получения последней записи инвентаризации по каждому товару: {e}") return [] def get_records_by_product(self, product_id: str, limit: int = 100) -> List[InventoryRecord]: @@ -84,9 +94,11 @@ class InventoryRepository: ORDER BY scanned_at DESC LIMIT %s """, (product_id, limit)) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(records)} записей инвентаризации для товара {product_id}") + return records except Exception as e: - print(f"Ошибка получения записи инвентаризации по продукту {product_id}: {e}") + logger.error(f"Ошибка получения записи инвентаризации по продукту {product_id}: {e}") return [] def get_records_by_robot(self, robot_id: str, limit: int = 100) -> List[InventoryRecord]: @@ -99,9 +111,11 @@ class InventoryRepository: ORDER BY scanned_at DESC LIMIT %s """, (robot_id, limit)) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(records)} записей инвентаризации для робота {robot_id}") + return records except Exception as e: - print(f"Ошибка получения записи инвентаризации по роботу {robot_id}: {e}") + logger.error(f"Ошибка получения записи инвентаризации по роботу {robot_id}: {e}") return [] def get_records_by_zone(self, zone: str, limit: int = 100) -> List[InventoryRecord]: @@ -114,9 +128,11 @@ class InventoryRepository: ORDER BY scanned_at DESC LIMIT %s """, (zone, limit)) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(records)} записей инвентаризации для зоны {zone}") + return records except Exception as e: - print(f"Ошибка получения записи инвентаризации по зоне {zone}: {e}") + logger.error(f"Ошибка получения записи инвентаризации по зоне {zone}: {e}") return [] def get_critical_items(self, hours: int = 24) -> List[InventoryRecord]: @@ -129,7 +145,9 @@ class InventoryRepository: AND scanned_at >= NOW() - INTERVAL '%s hours' ORDER BY scanned_at DESC """, (hours,)) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(records)} критических товаров за последние {hours} часов") + return records except Exception as e: - print(f"Ошибка получения товаров с критическим статусом: {e}") + logger.error(f"Ошибка получения товаров с критическим статусом: {e}") return [] diff --git a/db/repositories/product_repository.py b/db/repositories/product_repository.py index 8d55ce2..37154c6 100644 --- a/db/repositories/product_repository.py +++ b/db/repositories/product_repository.py @@ -1,8 +1,9 @@ -# db/repositories/product_repository.py from typing import List, Optional +import logging from db.connection import get_connection from model.product import Product +logger = logging.getLogger(__name__) class ProductRepository: def get_all(self) -> List[Product]: @@ -10,7 +11,7 @@ class ProductRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM products ORDER BY id") - return [ + products = [ Product( id=row[0], name=row[1], @@ -19,8 +20,10 @@ class ProductRepository: optimal_stock=row[4] ) for row in cur.fetchall() ] + logger.info(f"Получено {len(products)} товаров") + return products except Exception as e: - print(f"Ошибка получения продуктов: {e}") + logger.error(f"Ошибка получения продуктов: {e}") return [] def get_by_id(self, product_id: str) -> Optional[Product]: @@ -29,9 +32,13 @@ class ProductRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM products WHERE id = %s", (product_id,)) row = cur.fetchone() - return Product(*row) if row else None + if row: + logger.info(f"Товар {product_id} найден") + return Product(*row) + logger.warning(f"Товар {product_id} не найден") + return None except Exception as e: - print(f"Ошибка получения товара {product_id}: {e}") + logger.error(f"Ошибка получения товара {product_id}: {e}") return None def get_by_category(self, category: str) -> List[Product]: @@ -39,9 +46,11 @@ class ProductRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM products WHERE category = %s ORDER BY name", (category,)) - return [Product(*row) for row in cur.fetchall()] + products = [Product(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(products)} товаров категории {category}") + return products except Exception as e: - print(f"Ошибка получения товаров по категории {category}: {e}") + logger.error(f"Ошибка получения товаров по категории {category}: {e}") return [] def create_product(self, product_id: str, name: str, category: str, @@ -54,9 +63,14 @@ class ProductRepository: VALUES (%s, %s, %s, %s, %s) """, (product_id, name, category, min_stock, optimal_stock)) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.info(f"Создан новый товар {product_id}: {name}") + else: + logger.warning(f"Не удалось создать товар {product_id}") + return success except Exception as e: - print(f"Ошибка создания товара {product_id}: {e}") + logger.error(f"Ошибка создания товара {product_id}: {e}") return False def update_product(self, product_id: str, name: str = None, category: str = None, @@ -84,6 +98,7 @@ class ProductRepository: params.append(optimal_stock) if not updates: + logger.warning(f"Нет данных для обновления товара {product_id}") return False params.append(product_id) @@ -91,9 +106,14 @@ class ProductRepository: cur.execute(query, params) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.info(f"Товар {product_id} успешно обновлен") + else: + logger.warning(f"Товар {product_id} не найден для обновления") + return success except Exception as e: - print(f"Ошибка обновления товара {product_id}: {e}") + logger.error(f"Ошибка обновления товара {product_id}: {e}") return False def delete_product(self, product_id: str) -> bool: @@ -102,9 +122,14 @@ class ProductRepository: with conn.cursor() as cur: cur.execute("DELETE FROM products WHERE id = %s", (product_id,)) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.info(f"Товар {product_id} удален") + else: + logger.warning(f"Товар {product_id} не найден для удаления") + return success except Exception as e: - print(f"Ошибка удаления товара {product_id}: {e}") + logger.error(f"Ошибка удаления товара {product_id}: {e}") return False def search_products(self, search_term: str) -> List[Product]: @@ -116,9 +141,11 @@ class ProductRepository: WHERE name ILIKE %s OR id ILIKE %s ORDER BY name """, (f'%{search_term}%', f'%{search_term}%')) - return [Product(*row) for row in cur.fetchall()] + products = [Product(*row) for row in cur.fetchall()] + logger.info(f"Найдено {len(products)} товаров по запросу '{search_term}'") + return products except Exception as e: - print(f"Ошибка поиска товров по названию '{search_term}': {e}") + logger.error(f"Ошибка поиска товаров по названию '{search_term}': {e}") return [] def get_low_stock_products(self) -> List[Product]: @@ -133,7 +160,9 @@ class ProductRepository: AND ih.scanned_at >= NOW() - INTERVAL '1 day' ORDER BY p.name """) - return [Product(*row) for row in cur.fetchall()] + products = [Product(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(products)} товаров с низким запасом") + return products except Exception as e: - print(f"Ошибка получения товаров с низким запасом: {e}") + logger.error(f"Ошибка получения товаров с низким запасом: {e}") return [] \ No newline at end of file diff --git a/db/repositories/robot_repository.py b/db/repositories/robot_repository.py index a259e34..c460562 100644 --- a/db/repositories/robot_repository.py +++ b/db/repositories/robot_repository.py @@ -1,15 +1,18 @@ # db/repositories/robot_repository.py from typing import List, Optional +import logging from db.connection import get_connection from model.robot import Robot +logger = logging.getLogger(__name__) + class RobotRepository: def get_all(self) -> List[Robot]: try: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM robots ORDER BY id") - return [ + robots = [ Robot( id=row[0], status=row[1], @@ -20,8 +23,10 @@ class RobotRepository: current_shelf=row[6] ) for row in cur.fetchall() ] + logger.info(f"Получено {len(robots)} роботов") + return robots except Exception as e: - print(f"Ошика получения всех роботов: {e}") + logger.error(f"Ошибка получения всех роботов: {e}") return [] def get_by_id(self, robot_id: str) -> Optional[Robot]: @@ -30,9 +35,13 @@ class RobotRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM robots WHERE id = %s", (robot_id,)) row = cur.fetchone() - return Robot(*row) if row else None + if row: + logger.info(f"Робот {robot_id} найден") + return Robot(*row) + logger.warning(f"Робот {robot_id} не найден") + return None except Exception as e: - print(f"Ошибка получения роботов {robot_id}: {e}") + logger.error(f"Ошибка получения робота {robot_id}: {e}") return None def update_robot(self, robot_id: str, status: str = None, battery_level: int = None, @@ -68,9 +77,14 @@ class RobotRepository: cur.execute(query, params) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.info(f"Робот {robot_id} успешно обновлен") + else: + logger.warning(f"Робот {robot_id} не найден для обновления") + return success except Exception as e: - print(f"Ошибка обновления робота {robot_id}: {e}") + logger.error(f"Ошибка обновления робота {robot_id}: {e}") return False def get_robots_by_status(self, status: str) -> List[Robot]: @@ -78,9 +92,11 @@ class RobotRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM robots WHERE status = %s ORDER BY id", (status,)) - return [Robot(*row) for row in cur.fetchall()] + robots = [Robot(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(robots)} роботов со статусом {status}") + return robots except Exception as e: - print(f"Ошибка получения роботов по статусу {status}: {e}") + logger.error(f"Ошибка получения роботов по статусу {status}: {e}") return [] def get_low_battery_robots(self, threshold: int = 20) -> List[Robot]: @@ -92,9 +108,11 @@ class RobotRepository: WHERE battery_level < %s AND status = 'active' ORDER BY battery_level ASC """, (threshold,)) - return [Robot(*row) for row in cur.fetchall()] + robots = [Robot(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(robots)} роботов с низким зарядом (<{threshold}%)") + return robots except Exception as e: - print(f"Ошибка получения роботов с низким зарядом: {e}") + logger.error(f"Ошибка получения роботов с низким зарядом: {e}") return [] def get_robots_in_zone(self, zone: str) -> List[Robot]: @@ -102,9 +120,11 @@ class RobotRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM robots WHERE current_zone = %s ORDER BY id", (zone,)) - return [Robot(*row) for row in cur.fetchall()] + robots = [Robot(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(robots)} роботов в зоне {zone}") + return robots except Exception as e: - print(f"Ошибка получения роботов в зоне {zone}: {e}") + logger.error(f"Ошибка получения роботов в зоне {zone}: {e}") return [] def create_robot(self, robot_id: str, status: str = 'active', battery_level: int = 100) -> bool: @@ -116,9 +136,14 @@ class RobotRepository: VALUES (%s, %s, %s, CURRENT_TIMESTAMP) """, (robot_id, status, battery_level)) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.info(f"Создан новый робот {robot_id}") + else: + logger.warning(f"Не удалось создать робота {robot_id}") + return success except Exception as e: - print(f"Ошибка создания робота {robot_id}: {e}") + logger.error(f"Ошибка создания робота {robot_id}: {e}") return False def delete_robot(self, robot_id: str) -> bool: @@ -127,7 +152,12 @@ class RobotRepository: with conn.cursor() as cur: cur.execute("DELETE FROM robots WHERE id = %s", (robot_id,)) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.info(f"Робот {robot_id} удален") + else: + logger.warning(f"Робот {robot_id} не найден для удаления") + return success except Exception as e: - print(f"Ошибка удаления робота {robot_id}: {e}") + logger.error(f"Ошибка удаления робота {robot_id}: {e}") return False \ No newline at end of file diff --git a/db/repositories/user_repository.py b/db/repositories/user_repository.py index 7b1d979..31f527e 100644 --- a/db/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -1,130 +1,213 @@ from typing import List, Optional +import logging from model.user import User from db.connection import get_connection +logger = logging.getLogger(__name__) + class UserRepository: def get_all(self) -> List[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT * FROM users ORDER BY id") - return [ - User( - id=row[0], - email=row[1], - password_hash=row[2], - name=row[3], - role=row[4], - created_at=row[5] - ) for row in cur.fetchall() - ] + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users ORDER BY id") + users = [ + User( + id=row[0], + email=row[1], + password_hash=row[2], + name=row[3], + role=row[4], + created_at=row[5] + ) for row in cur.fetchall() + ] + logger.info(f"Получено {len(users)} пользователей") + return users + except Exception as e: + logger.error(f"Ошибка получения пользователей: {e}") + return [] def get_by_id(self, user_id: int) -> Optional[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT * FROM users WHERE id = %s", (user_id,)) - row = cur.fetchone() - if row: - return User(*row) - return None + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + row = cur.fetchone() + if row: + logger.info(f"Пользователь {user_id} найден") + return User(*row) + logger.warning(f"Пользователь {user_id} не найден") + return None + except Exception as e: + logger.error(f"Ошибка получения пользователя {user_id}: {e}") + return None def get_by_email(self, email: str) -> Optional[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT * FROM users WHERE email = %s", (email,)) - row = cur.fetchone() - if row: - return User(*row) - return None + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE email = %s", (email,)) + row = cur.fetchone() + if row: + logger.info(f"Пользователь с email {email} найден") + return User(*row) + logger.warning(f"Пользователь с email {email} не найден") + return None + except Exception as e: + logger.error(f"Ошибка получения пользователя по email {email}: {e}") + return None def create_user(self, email: str, password_hash: str, name: str, role: str) -> Optional[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute(""" - INSERT INTO users (email, password_hash, name, role) - VALUES (%s, %s, %s, %s) - RETURNING id, email, password_hash, name, role, created_at - """, (email, password_hash, name, role)) + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO users (email, password_hash, name, role) + VALUES (%s, %s, %s, %s) + RETURNING id, email, password_hash, name, role, created_at + """, (email, password_hash, name, role)) - row = cur.fetchone() - conn.commit() + row = cur.fetchone() + conn.commit() - if row: - return User(*row) - return None + if row: + logger.info(f"Создан новый пользователь {email} с ID {row[0]}") + return User(*row) + logger.warning(f"Не удалось создать пользователя {email}") + return None + except Exception as e: + logger.error(f"Ошибка создания пользователя {email}: {e}") + return None def update_user(self, user_id: int, name: str = None, role: str = None) -> bool: - with get_connection() as conn: - with conn.cursor() as cur: - updates = [] - params = [] + try: + with get_connection() as conn: + with conn.cursor() as cur: + updates = [] + params = [] - if name is not None: - updates.append("name = %s") - params.append(name) + if name is not None: + updates.append("name = %s") + params.append(name) - if role is not None: - updates.append("role = %s") - params.append(role) + if role is not None: + updates.append("role = %s") + params.append(role) - if not updates: - return False + if not updates: + logger.warning(f"Нет данных для обновления пользователя {user_id}") + return False - params.append(user_id) - query = f"UPDATE users SET {', '.join(updates)} WHERE id = %s" + params.append(user_id) + query = f"UPDATE users SET {', '.join(updates)} WHERE id = %s" - cur.execute(query, params) - conn.commit() - return cur.rowcount > 0 + cur.execute(query, params) + conn.commit() + success = cur.rowcount > 0 + if success: + logger.info(f"Пользователь {user_id} успешно обновлен") + else: + logger.warning(f"Пользователь {user_id} не найден для обновления") + return success + except Exception as e: + logger.error(f"Ошибка обновления пользователя {user_id}: {e}") + return False def delete_user(self, user_id: int) -> bool: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("DELETE FROM users WHERE id = %s", (user_id,)) - conn.commit() - return cur.rowcount > 0 + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM users WHERE id = %s", (user_id,)) + conn.commit() + success = cur.rowcount > 0 + if success: + logger.info(f"Пользователь {user_id} удален") + else: + logger.warning(f"Пользователь {user_id} не найден для удаления") + return success + except Exception as e: + logger.error(f"Ошибка удаления пользователя {user_id}: {e}") + return False def get_users_by_role(self, role: str) -> List[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT * FROM users WHERE role = %s ORDER BY name", (role,)) - return [ - User(*row) for row in cur.fetchall() - ] + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE role = %s ORDER BY name", (role,)) + users = [User(*row) for row in cur.fetchall()] + logger.info(f"Получено {len(users)} пользователей с ролью {role}") + return users + except Exception as e: + logger.error(f"Ошибка получения пользователей по роли {role}: {e}") + return [] def change_password(self, user_id: int, new_password_hash: str) -> bool: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute(""" - UPDATE users - SET password_hash = %s - WHERE id = %s - """, (new_password_hash, user_id)) - conn.commit() - return cur.rowcount > 0 + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + UPDATE users + SET password_hash = %s + WHERE id = %s + """, (new_password_hash, user_id)) + conn.commit() + success = cur.rowcount > 0 + if success: + logger.info(f"Пароль пользователя {user_id} изменен") + else: + logger.warning(f"Пользователь {user_id} не найден для смены пароля") + return success + except Exception as e: + logger.error(f"Ошибка смены пароля пользователя {user_id}: {e}") + return False def authenticate_user(self, email: str, password_hash: str) -> Optional[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute(""" - SELECT * FROM users - WHERE email = %s AND password_hash = %s - """, (email, password_hash)) - row = cur.fetchone() - if row: - return User(*row) - return None + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM users + WHERE email = %s AND password_hash = %s + """, (email, password_hash)) + row = cur.fetchone() + if row: + logger.info(f"Успешная аутентификация пользователя {email}") + return User(*row) + logger.warning(f"Неудачная аутентификация пользователя {email}") + return None + except Exception as e: + logger.error(f"Ошибка аутентификации пользователя {email}: {e}") + return None def is_valid_authenticate(self, email: str, password_hash: str) -> bool: - 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)) - return cur.fetchone() is not None + 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.info(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: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT 1 FROM users WHERE email = %s", (email,)) - return cur.fetchone() is not None \ No newline at end of file + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT 1 FROM users WHERE email = %s", (email,)) + exists = cur.fetchone() is not None + if exists: + logger.info(f"Пользователь с email {email} существует") + else: + logger.info(f"Пользователь с email {email} не существует") + return exists + except Exception as e: + logger.error(f"Ошибка проверки существования пользователя {email}: {e}") + return False diff --git a/model/ai_prediction.py b/model/ai_prediction.py index 1685105..acf9978 100644 --- a/model/ai_prediction.py +++ b/model/ai_prediction.py @@ -9,4 +9,4 @@ class AIPrediction: days_until_stockout: int recommended_order: int confidence_score: float - created_at: datetime \ No newline at end of file + created_at: datetime diff --git a/model/inventory.py b/model/inventory.py index dc7820d..9f42e08 100644 --- a/model/inventory.py +++ b/model/inventory.py @@ -11,4 +11,4 @@ class InventoryRecord: shelf_number: int status: str scanned_at: datetime - created_at: datetime \ No newline at end of file + created_at: datetime diff --git a/model/product.py b/model/product.py index 8afdd95..4034b37 100644 --- a/model/product.py +++ b/model/product.py @@ -6,4 +6,4 @@ class Product: name: str category: str min_stock: int - optimal_stock: int \ No newline at end of file + optimal_stock: int diff --git a/model/robot.py b/model/robot.py index fd3847e..2e68e18 100644 --- a/model/robot.py +++ b/model/robot.py @@ -10,4 +10,4 @@ class Robot: last_update: datetime current_zone: Optional[str] = None current_row: Optional[int] = None - current_shelf: Optional[int] = None \ No newline at end of file + current_shelf: Optional[int] = None diff --git a/model/user.py b/model/user.py index 9eda604..e9ea4da 100644 --- a/model/user.py +++ b/model/user.py @@ -9,4 +9,4 @@ class User: password_hash: str name: str role: str - created_at: datetime \ No newline at end of file + created_at: datetime -- 2.52.0 From 401205aea2274ba9a522e7e41910fef135ab7fcb Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 19:41:11 +0300 Subject: [PATCH 14/40] style(database): Fixed the issue with long function declarations --- db/repositories/ai_prediction_repository.py | 7 +++++-- db/repositories/inventory_repository.py | 11 +++++++++-- db/repositories/robot_repository.py | 15 +++++++++++---- db/repositories/user_repository.py | 6 +++++- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/db/repositories/ai_prediction_repository.py b/db/repositories/ai_prediction_repository.py index afa25b0..66a60ec 100644 --- a/db/repositories/ai_prediction_repository.py +++ b/db/repositories/ai_prediction_repository.py @@ -77,8 +77,11 @@ class AIPredictionsRepository: logger.error(f"Ошибка получения последних прогнозов по товарам: {e}") return [] - def create_prediction(self, product_id: str, prediction_date: date, - days_until_stockout: int, recommended_order: int, + def create_prediction(self, + product_id: str, + prediction_date: date, + days_until_stockout: int, + recommended_order: int, confidence_score: float) -> Optional[int]: try: with get_connection() as conn: diff --git a/db/repositories/inventory_repository.py b/db/repositories/inventory_repository.py index 365f48f..a3c3536 100644 --- a/db/repositories/inventory_repository.py +++ b/db/repositories/inventory_repository.py @@ -48,8 +48,15 @@ class InventoryRepository: logger.error(f"Ошибка получения записи инвентаризации {record_id}: {e}") return None - def create_record(self, robot_id: str, product_id: str, quantity: int, zone: str, - row_number: int, shelf_number: int, status: str, scanned_at: datetime) -> Optional[int]: + def create_record(self, + robot_id: str, + product_id: str, + quantity: int, + zone: str, + row_number: int, + shelf_number: int, + status: str, + scanned_at: datetime) -> Optional[int]: try: with get_connection() as conn: with conn.cursor() as cur: diff --git a/db/repositories/robot_repository.py b/db/repositories/robot_repository.py index c460562..4279355 100644 --- a/db/repositories/robot_repository.py +++ b/db/repositories/robot_repository.py @@ -1,4 +1,3 @@ -# db/repositories/robot_repository.py from typing import List, Optional import logging from db.connection import get_connection @@ -44,8 +43,13 @@ class RobotRepository: logger.error(f"Ошибка получения робота {robot_id}: {e}") return None - def update_robot(self, robot_id: str, status: str = None, battery_level: int = None, - current_zone: str = None, current_row: int = None, current_shelf: int = None) -> bool: + def update_robot(self, + robot_id: str, + status: str = None, + battery_level: int = None, + current_zone: str = None, + current_row: int = None, + current_shelf: int = None) -> bool: try: with get_connection() as conn: with conn.cursor() as cur: @@ -127,7 +131,10 @@ class RobotRepository: logger.error(f"Ошибка получения роботов в зоне {zone}: {e}") return [] - def create_robot(self, robot_id: str, status: str = 'active', battery_level: int = 100) -> bool: + def create_robot(self, + robot_id: str, + status: str = 'active', + battery_level: int = 100) -> bool: try: with get_connection() as conn: with conn.cursor() as cur: diff --git a/db/repositories/user_repository.py b/db/repositories/user_repository.py index 31f527e..17ac782 100644 --- a/db/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -57,7 +57,11 @@ class UserRepository: logger.error(f"Ошибка получения пользователя по email {email}: {e}") return None - def create_user(self, email: str, password_hash: str, name: str, role: str) -> Optional[User]: + def create_user(self, + email: str, + password_hash: str, + name: str, + role: str) -> Optional[User]: try: with get_connection() as conn: with conn.cursor() as cur: -- 2.52.0 From c33e8798edd9517cd4b9576dcda5e251bdb018d6 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:31:51 +0300 Subject: [PATCH 15/40] style(database): Fixed type of logger on the db sucsess operations --- db/connection.py | 6 +++--- db/repositories/ai_prediction_repository.py | 12 +++++------ db/repositories/inventory_repository.py | 16 +++++++------- db/repositories/product_repository.py | 16 +++++++------- db/repositories/robot_repository.py | 16 +++++++------- db/repositories/user_repository.py | 24 ++++++++++----------- 6 files changed, 45 insertions(+), 45 deletions(-) diff --git a/db/connection.py b/db/connection.py index c966677..3950913 100644 --- a/db/connection.py +++ b/db/connection.py @@ -24,7 +24,7 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: conn = None try: conn = PSQLConnect() - logger.info("Подключение к БД установлено") + logger.debug("Подключение к БД установлено") yield conn except psycopg2.OperationalError as e: logger.error(f"Ошибка подключения к БД: {e}") @@ -39,7 +39,7 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: if conn: try: conn.close() - logger.info("Соединение с БД закрыто") + logger.debug("Соединение с БД закрыто") except Exception as e: logger.warning(f"Ошибка при закрытии соединения: {e}") @@ -50,7 +50,7 @@ def test_connection() -> bool: try: cur.execute("SELECT version();") version = cur.fetchone() - logger.info(f"Версия PostgreSQL: {version[0]}") + logger.debug(f"Версия PostgreSQL: {version[0]}") return True finally: cur.close() diff --git a/db/repositories/ai_prediction_repository.py b/db/repositories/ai_prediction_repository.py index 66a60ec..11fde40 100644 --- a/db/repositories/ai_prediction_repository.py +++ b/db/repositories/ai_prediction_repository.py @@ -23,7 +23,7 @@ class AIPredictionsRepository: created_at=row[6] ) for row in cur.fetchall() ] - logger.info(f"Получено {len(predictions)} прогнозов") + logger.debug(f"Получено {len(predictions)} прогнозов") return predictions except Exception as e: logger.error(f"Ошибка получения прогнозов: {e}") @@ -36,7 +36,7 @@ class AIPredictionsRepository: cur.execute("SELECT * FROM ai_predictions WHERE id = %s", (prediction_id,)) row = cur.fetchone() if row: - logger.info(f"Прогноз {prediction_id} найден") + logger.debug(f"Прогноз {prediction_id} найден") return AIPrediction(*row) logger.warning(f"Прогноз {prediction_id} не найден") return None @@ -55,7 +55,7 @@ class AIPredictionsRepository: LIMIT %s """, (product_id, limit)) predictions = [AIPrediction(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(predictions)} прогнозов для товара {product_id}") + logger.debug(f"Получено {len(predictions)} прогнозов для товара {product_id}") return predictions except Exception as e: logger.error(f"Ошибка получения прогноза по товару {product_id}: {e}") @@ -71,7 +71,7 @@ class AIPredictionsRepository: ORDER BY product_id, prediction_date DESC """) predictions = [AIPrediction(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(predictions)} последних прогнозов по товарам") + logger.debug(f"Получено {len(predictions)} последних прогнозов по товарам") return predictions except Exception as e: logger.error(f"Ошибка получения последних прогнозов по товарам: {e}") @@ -95,7 +95,7 @@ class AIPredictionsRepository: prediction_id = cur.fetchone()[0] conn.commit() - logger.info(f"Создан новый прогноз ID: {prediction_id} для товара {product_id}") + logger.debug(f"Создан новый прогноз ID: {prediction_id} для товара {product_id}") return prediction_id except Exception as e: logger.error(f"Ошибка создания прогноза: {e}") @@ -111,7 +111,7 @@ class AIPredictionsRepository: """, (older_than_days,)) deleted_count = cur.rowcount conn.commit() - logger.info(f"Удалено {deleted_count} старых прогнозов старше {older_than_days} дней") + logger.debug(f"Удалено {deleted_count} старых прогнозов старше {older_than_days} дней") return deleted_count except Exception as e: logger.error(f"Ошибка удаления старых прогнозов: {e}") diff --git a/db/repositories/inventory_repository.py b/db/repositories/inventory_repository.py index a3c3536..b514acb 100644 --- a/db/repositories/inventory_repository.py +++ b/db/repositories/inventory_repository.py @@ -27,7 +27,7 @@ class InventoryRepository: created_at=row[9] ) for row in cur.fetchall() ] - logger.info(f"Получено {len(records)} записей инвентаризации") + logger.debug(f"Получено {len(records)} записей инвентаризации") return records except Exception as e: logger.error(f"Ошибка получения истории инвентаризации: {e}") @@ -40,7 +40,7 @@ class InventoryRepository: cur.execute("SELECT * FROM inventory_history WHERE id = %s", (record_id,)) row = cur.fetchone() if row: - logger.info(f"Запись инвентаризации {record_id} найдена") + logger.debug(f"Запись инвентаризации {record_id} найдена") return InventoryRecord(*row) logger.warning(f"Запись инвентаризации {record_id} не найдена") return None @@ -69,7 +69,7 @@ class InventoryRepository: record_id = cur.fetchone()[0] conn.commit() - logger.info(f"Создана новая запись инвентаризации ID: {record_id}") + logger.debug(f"Создана новая запись инвентаризации ID: {record_id}") return record_id except Exception as e: logger.error(f"Ошибка создания записи инвентаризации: {e}") @@ -85,7 +85,7 @@ class InventoryRepository: ORDER BY product_id, scanned_at DESC """) records = [InventoryRecord(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(records)} последних записей инвентаризации по товарам") + logger.debug(f"Получено {len(records)} последних записей инвентаризации по товарам") return records except Exception as e: logger.error(f"Ошибка получения последней записи инвентаризации по каждому товару: {e}") @@ -102,7 +102,7 @@ class InventoryRepository: LIMIT %s """, (product_id, limit)) records = [InventoryRecord(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(records)} записей инвентаризации для товара {product_id}") + logger.debug(f"Получено {len(records)} записей инвентаризации для товара {product_id}") return records except Exception as e: logger.error(f"Ошибка получения записи инвентаризации по продукту {product_id}: {e}") @@ -119,7 +119,7 @@ class InventoryRepository: LIMIT %s """, (robot_id, limit)) records = [InventoryRecord(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(records)} записей инвентаризации для робота {robot_id}") + logger.debug(f"Получено {len(records)} записей инвентаризации для робота {robot_id}") return records except Exception as e: logger.error(f"Ошибка получения записи инвентаризации по роботу {robot_id}: {e}") @@ -136,7 +136,7 @@ class InventoryRepository: LIMIT %s """, (zone, limit)) records = [InventoryRecord(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(records)} записей инвентаризации для зоны {zone}") + logger.debug(f"Получено {len(records)} записей инвентаризации для зоны {zone}") return records except Exception as e: logger.error(f"Ошибка получения записи инвентаризации по зоне {zone}: {e}") @@ -153,7 +153,7 @@ class InventoryRepository: ORDER BY scanned_at DESC """, (hours,)) records = [InventoryRecord(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(records)} критических товаров за последние {hours} часов") + logger.debug(f"Получено {len(records)} критических товаров за последние {hours} часов") return records except Exception as e: logger.error(f"Ошибка получения товаров с критическим статусом: {e}") diff --git a/db/repositories/product_repository.py b/db/repositories/product_repository.py index 37154c6..6de73b8 100644 --- a/db/repositories/product_repository.py +++ b/db/repositories/product_repository.py @@ -20,7 +20,7 @@ class ProductRepository: optimal_stock=row[4] ) for row in cur.fetchall() ] - logger.info(f"Получено {len(products)} товаров") + logger.debug(f"Получено {len(products)} товаров") return products except Exception as e: logger.error(f"Ошибка получения продуктов: {e}") @@ -33,7 +33,7 @@ class ProductRepository: cur.execute("SELECT * FROM products WHERE id = %s", (product_id,)) row = cur.fetchone() if row: - logger.info(f"Товар {product_id} найден") + logger.debug(f"Товар {product_id} найден") return Product(*row) logger.warning(f"Товар {product_id} не найден") return None @@ -47,7 +47,7 @@ class ProductRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM products WHERE category = %s ORDER BY name", (category,)) products = [Product(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(products)} товаров категории {category}") + logger.debug(f"Получено {len(products)} товаров категории {category}") return products except Exception as e: logger.error(f"Ошибка получения товаров по категории {category}: {e}") @@ -65,7 +65,7 @@ class ProductRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Создан новый товар {product_id}: {name}") + logger.debug(f"Создан новый товар {product_id}: {name}") else: logger.warning(f"Не удалось создать товар {product_id}") return success @@ -108,7 +108,7 @@ class ProductRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Товар {product_id} успешно обновлен") + logger.debug(f"Товар {product_id} успешно обновлен") else: logger.warning(f"Товар {product_id} не найден для обновления") return success @@ -124,7 +124,7 @@ class ProductRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Товар {product_id} удален") + logger.debug(f"Товар {product_id} удален") else: logger.warning(f"Товар {product_id} не найден для удаления") return success @@ -142,7 +142,7 @@ class ProductRepository: ORDER BY name """, (f'%{search_term}%', f'%{search_term}%')) products = [Product(*row) for row in cur.fetchall()] - logger.info(f"Найдено {len(products)} товаров по запросу '{search_term}'") + logger.debug(f"Найдено {len(products)} товаров по запросу '{search_term}'") return products except Exception as e: logger.error(f"Ошибка поиска товаров по названию '{search_term}': {e}") @@ -161,7 +161,7 @@ class ProductRepository: ORDER BY p.name """) products = [Product(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(products)} товаров с низким запасом") + logger.debug(f"Получено {len(products)} товаров с низким запасом") return products except Exception as e: logger.error(f"Ошибка получения товаров с низким запасом: {e}") diff --git a/db/repositories/robot_repository.py b/db/repositories/robot_repository.py index 4279355..3092502 100644 --- a/db/repositories/robot_repository.py +++ b/db/repositories/robot_repository.py @@ -22,7 +22,7 @@ class RobotRepository: current_shelf=row[6] ) for row in cur.fetchall() ] - logger.info(f"Получено {len(robots)} роботов") + logger.debug(f"Получено {len(robots)} роботов") return robots except Exception as e: logger.error(f"Ошибка получения всех роботов: {e}") @@ -35,7 +35,7 @@ class RobotRepository: cur.execute("SELECT * FROM robots WHERE id = %s", (robot_id,)) row = cur.fetchone() if row: - logger.info(f"Робот {robot_id} найден") + logger.debug(f"Робот {robot_id} найден") return Robot(*row) logger.warning(f"Робот {robot_id} не найден") return None @@ -83,7 +83,7 @@ class RobotRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Робот {robot_id} успешно обновлен") + logger.debug(f"Робот {robot_id} успешно обновлен") else: logger.warning(f"Робот {robot_id} не найден для обновления") return success @@ -97,7 +97,7 @@ class RobotRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM robots WHERE status = %s ORDER BY id", (status,)) robots = [Robot(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(robots)} роботов со статусом {status}") + logger.debug(f"Получено {len(robots)} роботов со статусом {status}") return robots except Exception as e: logger.error(f"Ошибка получения роботов по статусу {status}: {e}") @@ -113,7 +113,7 @@ class RobotRepository: ORDER BY battery_level ASC """, (threshold,)) robots = [Robot(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(robots)} роботов с низким зарядом (<{threshold}%)") + logger.debug(f"Получено {len(robots)} роботов с низким зарядом (<{threshold}%)") return robots except Exception as e: logger.error(f"Ошибка получения роботов с низким зарядом: {e}") @@ -125,7 +125,7 @@ class RobotRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM robots WHERE current_zone = %s ORDER BY id", (zone,)) robots = [Robot(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(robots)} роботов в зоне {zone}") + logger.debug(f"Получено {len(robots)} роботов в зоне {zone}") return robots except Exception as e: logger.error(f"Ошибка получения роботов в зоне {zone}: {e}") @@ -145,7 +145,7 @@ class RobotRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Создан новый робот {robot_id}") + logger.debug(f"Создан новый робот {robot_id}") else: logger.warning(f"Не удалось создать робота {robot_id}") return success @@ -161,7 +161,7 @@ class RobotRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Робот {robot_id} удален") + logger.debug(f"Робот {robot_id} удален") else: logger.warning(f"Робот {robot_id} не найден для удаления") return success diff --git a/db/repositories/user_repository.py b/db/repositories/user_repository.py index 17ac782..e6307fb 100644 --- a/db/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -21,7 +21,7 @@ class UserRepository: created_at=row[5] ) for row in cur.fetchall() ] - logger.info(f"Получено {len(users)} пользователей") + logger.debug(f"Получено {len(users)} пользователей") return users except Exception as e: logger.error(f"Ошибка получения пользователей: {e}") @@ -34,7 +34,7 @@ class UserRepository: cur.execute("SELECT * FROM users WHERE id = %s", (user_id,)) row = cur.fetchone() if row: - logger.info(f"Пользователь {user_id} найден") + logger.debug(f"Пользователь {user_id} найден") return User(*row) logger.warning(f"Пользователь {user_id} не найден") return None @@ -49,7 +49,7 @@ class UserRepository: cur.execute("SELECT * FROM users WHERE email = %s", (email,)) row = cur.fetchone() if row: - logger.info(f"Пользователь с email {email} найден") + logger.debug(f"Пользователь с email {email} найден") return User(*row) logger.warning(f"Пользователь с email {email} не найден") return None @@ -75,7 +75,7 @@ class UserRepository: conn.commit() if row: - logger.info(f"Создан новый пользователь {email} с ID {row[0]}") + logger.debug(f"Создан новый пользователь {email} с ID {row[0]}") return User(*row) logger.warning(f"Не удалось создать пользователя {email}") return None @@ -109,7 +109,7 @@ class UserRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Пользователь {user_id} успешно обновлен") + logger.debug(f"Пользователь {user_id} успешно обновлен") else: logger.warning(f"Пользователь {user_id} не найден для обновления") return success @@ -125,7 +125,7 @@ class UserRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Пользователь {user_id} удален") + logger.debug(f"Пользователь {user_id} удален") else: logger.warning(f"Пользователь {user_id} не найден для удаления") return success @@ -139,7 +139,7 @@ class UserRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM users WHERE role = %s ORDER BY name", (role,)) users = [User(*row) for row in cur.fetchall()] - logger.info(f"Получено {len(users)} пользователей с ролью {role}") + logger.debug(f"Получено {len(users)} пользователей с ролью {role}") return users except Exception as e: logger.error(f"Ошибка получения пользователей по роли {role}: {e}") @@ -157,7 +157,7 @@ class UserRepository: conn.commit() success = cur.rowcount > 0 if success: - logger.info(f"Пароль пользователя {user_id} изменен") + logger.debug(f"Пароль пользователя {user_id} изменен") else: logger.warning(f"Пользователь {user_id} не найден для смены пароля") return success @@ -175,7 +175,7 @@ class UserRepository: """, (email, password_hash)) row = cur.fetchone() if row: - logger.info(f"Успешная аутентификация пользователя {email}") + logger.debug(f"Успешная аутентификация пользователя {email}") return User(*row) logger.warning(f"Неудачная аутентификация пользователя {email}") return None @@ -193,7 +193,7 @@ class UserRepository: """, (email, password_hash)) is_valid = cur.fetchone() is not None if is_valid: - logger.info(f"Валидные учетные данные для пользователя {email}") + logger.debug(f"Валидные учетные данные для пользователя {email}") else: logger.warning(f"Невалидные учетные данные для пользователя {email}") return is_valid @@ -208,9 +208,9 @@ class UserRepository: cur.execute("SELECT 1 FROM users WHERE email = %s", (email,)) exists = cur.fetchone() is not None if exists: - logger.info(f"Пользователь с email {email} существует") + logger.debug(f"Пользователь с email {email} существует") else: - logger.info(f"Пользователь с email {email} не существует") + logger.debug(f"Пользователь с email {email} не существует") return exists except Exception as e: logger.error(f"Ошибка проверки существования пользователя {email}: {e}") -- 2.52.0 From ec985a808de4d2832a3cf2c3ee868e2f4df07f72 Mon Sep 17 00:00:00 2001 From: Kirill Date: Sun, 26 Oct 2025 14:10:10 +0300 Subject: [PATCH 16/40] fix(api/auth/loginapi.py, app.py, utils/PostgressConnect.py, utils/createLogger.py, utils/loadDotEnv.py): Review fix https://g.codrs.ru/Hackaton/Backend/pulls/2#issuecomment-23 https://g.codrs.ru/Hackaton/Backend/pulls/2#issuecomment-31 --- api/auth/loginapi.py | 19 +++++++++++++------ app.py | 3 ++- utils/PostgressConnect.py | 6 +++--- utils/createLogger.py | 6 ++++++ utils/loadDotEnv.py | 7 +++++-- 5 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 utils/createLogger.py diff --git a/api/auth/loginapi.py b/api/auth/loginapi.py index 791432f..36dea16 100644 --- a/api/auth/loginapi.py +++ b/api/auth/loginapi.py @@ -1,12 +1,19 @@ from flask import Blueprint, request, jsonify from model.user import user +from utils.createLogger import createLogger loginBP = Blueprint("loginapi", __name__) - +log = createLogger("LoginAPI") @loginBP.route('/api/login', methods = ['POST']) def login(): - email = request.form['email'] - password = request.form['password'] - #if(isvalid(email, password)): - us = user(email, password) - return jsonify(us.toDictionary()) + if request.is_json: + req = request.json + email = req['email'] + password = req['password'] + #if(isvalid(email, password)): + us = user(email, password) + log.debug("Respons is sended") + return jsonify(us.toDictionary()) + else: + log.error("Request is not a JSON") + return "Request is not a json", 500 diff --git a/app.py b/app.py index 0d141d5..7b7d0ba 100644 --- a/app.py +++ b/app.py @@ -2,7 +2,8 @@ from flask import Flask from api.auth.loginapi import loginBP from utils.loadDotEnv import initializeENV -state = initializeENV() +if not initializeENV(): + exit(-1) app = Flask(__name__) diff --git a/utils/PostgressConnect.py b/utils/PostgressConnect.py index 6bc71a7..d420fbf 100644 --- a/utils/PostgressConnect.py +++ b/utils/PostgressConnect.py @@ -1,10 +1,10 @@ -import psycopg +import psycopg2 import os def PSQLConnect(): - conn = psycopg.connect(os.getenv('POSTDRESS_CONNECTION')) + conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) return conn def PSQLCursor(conn): - cur = conn.cursor() + cur = conn.cursor() return cur diff --git a/utils/createLogger.py b/utils/createLogger.py new file mode 100644 index 0000000..7071f0e --- /dev/null +++ b/utils/createLogger.py @@ -0,0 +1,6 @@ +import logging + +def createLogger(name: str): + logger = logging.getLogger() + logger.propagate = False + return logger diff --git a/utils/loadDotEnv.py b/utils/loadDotEnv.py index 1edf728..ec6c9d5 100644 --- a/utils/loadDotEnv.py +++ b/utils/loadDotEnv.py @@ -1,12 +1,15 @@ import os from dotenv import load_dotenv +from .createLogger import createLogger + +log = createLogger("ENV") def initializeENV(): dotenv_path = '../.env' if os.path.exists(dotenv_path): load_dotenv(dotenv_path) - print('.env is loaded') + log.info('.env is loaded') return 1 else: - print('.env isn`t loaded') + log.error('.env isn`t loaded') return 0 -- 2.52.0 From 96a310a80d4cf8ea03c1a723a3f780a70df0239d Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 14:55:43 +0300 Subject: [PATCH 17/40] fix(env): ensure the .env is loaded --- .env.tmp | 2 ++ app.py | 1 + dependencies.txt | 2 +- utils/PostgressConnect.py | 2 +- utils/loadDotEnv.py | 12 ++++++------ 5 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 .env.tmp diff --git a/.env.tmp b/.env.tmp new file mode 100644 index 0000000..d121f85 --- /dev/null +++ b/.env.tmp @@ -0,0 +1,2 @@ +KEY= # Key for JWT token +POSTGRES_URL=postgresql:// diff --git a/app.py b/app.py index 7b7d0ba..98e91d3 100644 --- a/app.py +++ b/app.py @@ -1,3 +1,4 @@ +from sys import exit from flask import Flask from api.auth.loginapi import loginBP from utils.loadDotEnv import initializeENV diff --git a/dependencies.txt b/dependencies.txt index 5758f3e..4fc4ead 100644 --- a/dependencies.txt +++ b/dependencies.txt @@ -1,4 +1,4 @@ flask==3.1.2 python-dotenv -psycopg-binary +psycopg2-binary pyjwt diff --git a/utils/PostgressConnect.py b/utils/PostgressConnect.py index d420fbf..ea6a485 100644 --- a/utils/PostgressConnect.py +++ b/utils/PostgressConnect.py @@ -2,7 +2,7 @@ import psycopg2 import os def PSQLConnect(): - conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) + conn = psycopg2.connect(os.getenv('POSTGRES_URL')) return conn def PSQLCursor(conn): diff --git a/utils/loadDotEnv.py b/utils/loadDotEnv.py index ec6c9d5..85bbc72 100644 --- a/utils/loadDotEnv.py +++ b/utils/loadDotEnv.py @@ -2,14 +2,14 @@ import os from dotenv import load_dotenv from .createLogger import createLogger +DOTENV_PATH = '.env' log = createLogger("ENV") -def initializeENV(): - dotenv_path = '../.env' - if os.path.exists(dotenv_path): - load_dotenv(dotenv_path) +def initializeENV() -> bool: + if os.path.exists(DOTENV_PATH): + load_dotenv(DOTENV_PATH) log.info('.env is loaded') - return 1 + return True else: log.error('.env isn`t loaded') - return 0 + return False -- 2.52.0 From df5317432ddfa00dd897aa9287c2c5122e93b4df Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 14:57:49 +0300 Subject: [PATCH 18/40] refactor(user): change class name to uppercase --- api/auth/loginapi.py | 6 +++--- model/user.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/auth/loginapi.py b/api/auth/loginapi.py index 36dea16..eaa0ca8 100644 --- a/api/auth/loginapi.py +++ b/api/auth/loginapi.py @@ -1,5 +1,5 @@ from flask import Blueprint, request, jsonify -from model.user import user +from model.user import User from utils.createLogger import createLogger loginBP = Blueprint("loginapi", __name__) @@ -11,9 +11,9 @@ def login(): email = req['email'] password = req['password'] #if(isvalid(email, password)): - us = user(email, password) + user = User(email, password) log.debug("Respons is sended") - return jsonify(us.toDictionary()) + return jsonify(user.toDictionary()) else: log.error("Request is not a JSON") return "Request is not a json", 500 diff --git a/model/user.py b/model/user.py index fcf8e0a..6ad8c5d 100644 --- a/model/user.py +++ b/model/user.py @@ -3,7 +3,7 @@ import json from utils.token import generateKey @dataclass -class user: +class User: id: int name: str role: str -- 2.52.0 From 67c06a020396f7520d7ebafb4a053ea3d7cd3cd1 Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 15:08:14 +0300 Subject: [PATCH 19/40] feat(auth): add data validation --- api/auth/loginapi.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/api/auth/loginapi.py b/api/auth/loginapi.py index eaa0ca8..27ffbea 100644 --- a/api/auth/loginapi.py +++ b/api/auth/loginapi.py @@ -1,19 +1,28 @@ from flask import Blueprint, request, jsonify from model.user import User -from utils.createLogger import createLogger loginBP = Blueprint("loginapi", __name__) -log = createLogger("LoginAPI") -@loginBP.route('/api/login', methods = ['POST']) + + +@loginBP.route('/api/auth/login', methods = ['POST']) def login(): if request.is_json: req = request.json - email = req['email'] - password = req['password'] - #if(isvalid(email, password)): + + email = req.get('email') + password = req.get('password') + + if not email or not password: + return "Request must have email and password", 400 + + if len(email.strip()) < 4 or '@' not in email or '.' not in email: + return "Email is incorrect", 400 + + if len(password.strip()) < 8: + return "Password is too short", 400 + user = User(email, password) - log.debug("Respons is sended") return jsonify(user.toDictionary()) + else: - log.error("Request is not a JSON") - return "Request is not a json", 500 + return "Request is not a json", 400 -- 2.52.0 From 4ad7869e272b1a7d89f2ee45e2c42e63461a3cfb Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 15:23:06 +0300 Subject: [PATCH 20/40] refactor(user): rename to toJson back --- api/auth/loginapi.py | 2 +- model/user.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/auth/loginapi.py b/api/auth/loginapi.py index 27ffbea..ab095e9 100644 --- a/api/auth/loginapi.py +++ b/api/auth/loginapi.py @@ -22,7 +22,7 @@ def login(): return "Password is too short", 400 user = User(email, password) - return jsonify(user.toDictionary()) + return jsonify(user.toJson()) else: return "Request is not a json", 400 diff --git a/model/user.py b/model/user.py index 6ad8c5d..03d51fa 100644 --- a/model/user.py +++ b/model/user.py @@ -16,5 +16,5 @@ class User: self.role = 'Backend'#us['role'] self.token = generateKey(email, passwd) - def toDictionary(self): + def toJson(self): return {"user": {"id": self.id, "name": self.name, "role": self.role}, "token": self.token} -- 2.52.0 From be5732bc32cd6ed04c340fe71635123210c4f1cb Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 15:21:48 +0300 Subject: [PATCH 21/40] refactor(auth): remove /api/auth/* handlers to api/auth.py --- api/{auth/loginapi.py => auth.py} | 4 ++-- app.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename api/{auth/loginapi.py => auth.py} (87%) diff --git a/api/auth/loginapi.py b/api/auth.py similarity index 87% rename from api/auth/loginapi.py rename to api/auth.py index ab095e9..0c357e8 100644 --- a/api/auth/loginapi.py +++ b/api/auth.py @@ -1,10 +1,10 @@ from flask import Blueprint, request, jsonify from model.user import User -loginBP = Blueprint("loginapi", __name__) +auth = Blueprint("auth", __name__) -@loginBP.route('/api/auth/login', methods = ['POST']) +@auth.route('/login', methods = ['POST']) def login(): if request.is_json: req = request.json diff --git a/app.py b/app.py index 98e91d3..0ccbfa1 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,6 @@ from sys import exit from flask import Flask -from api.auth.loginapi import loginBP +from api.auth import auth from utils.loadDotEnv import initializeENV if not initializeENV(): @@ -8,4 +8,4 @@ if not initializeENV(): app = Flask(__name__) -app.register_blueprint(loginBP) +app.register_blueprint(auth, url_prefix='/api/auth') -- 2.52.0 From 6f478d717a46c7d882d495ed8ee8ffbad9022fd2 Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 15:57:45 +0300 Subject: [PATCH 22/40] feat(log): use loguru --- dependencies.txt | 1 + utils/createLogger.py | 6 ------ utils/loadDotEnv.py | 3 +-- 3 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 utils/createLogger.py diff --git a/dependencies.txt b/dependencies.txt index 4fc4ead..69c3592 100644 --- a/dependencies.txt +++ b/dependencies.txt @@ -2,3 +2,4 @@ flask==3.1.2 python-dotenv psycopg2-binary pyjwt +loguru diff --git a/utils/createLogger.py b/utils/createLogger.py deleted file mode 100644 index 7071f0e..0000000 --- a/utils/createLogger.py +++ /dev/null @@ -1,6 +0,0 @@ -import logging - -def createLogger(name: str): - logger = logging.getLogger() - logger.propagate = False - return logger diff --git a/utils/loadDotEnv.py b/utils/loadDotEnv.py index 85bbc72..0d6c510 100644 --- a/utils/loadDotEnv.py +++ b/utils/loadDotEnv.py @@ -1,9 +1,8 @@ import os from dotenv import load_dotenv -from .createLogger import createLogger +from loguru import logger as log DOTENV_PATH = '.env' -log = createLogger("ENV") def initializeENV() -> bool: if os.path.exists(DOTENV_PATH): -- 2.52.0 From 70041fa58e312321382c88b41f9118286619602e Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 17:27:24 +0300 Subject: [PATCH 23/40] feat(jwt): add iat field --- utils/token.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/token.py b/utils/token.py index 90a69a9..d20cea4 100644 --- a/utils/token.py +++ b/utils/token.py @@ -1,7 +1,8 @@ import jwt import os +from time import time def generateKey(email, passwd): key = os.getenv('KEY') - encoded = jwt.encode({email: passwd}, key, algorithm="HS256") + encoded = jwt.encode({email: passwd, 'iat': time()}, key, algorithm="HS256") return encoded -- 2.52.0 From e80c5645f11d0e0d6711e64cf8aa5edcefd0f4f4 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:11:20 +0300 Subject: [PATCH 24/40] feat(model): Added correct data models --- model/__init__.py | 6 ++++++ model/inventory.py | 14 ++++++++++++++ model/product.py | 9 +++++++++ model/robot.py | 13 +++++++++++++ model/user.py | 16 ++++++++++++++-- 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 model/__init__.py create mode 100644 model/inventory.py create mode 100644 model/product.py create mode 100644 model/robot.py diff --git a/model/__init__.py b/model/__init__.py new file mode 100644 index 0000000..5e9673e --- /dev/null +++ b/model/__init__.py @@ -0,0 +1,6 @@ +from .robot import Robot +from .product import Product +from .inventory import InventoryRecord +from .user import User + +__all__ = ['Robot', 'Product', 'InventoryRecord', 'User'] \ No newline at end of file diff --git a/model/inventory.py b/model/inventory.py new file mode 100644 index 0000000..dc7820d --- /dev/null +++ b/model/inventory.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from datetime import datetime +@dataclass +class InventoryRecord: + id: int + robot_id: str + product_id: str + quantity: int + zone: str + row_number: int + shelf_number: int + status: str + scanned_at: datetime + created_at: datetime \ No newline at end of file diff --git a/model/product.py b/model/product.py new file mode 100644 index 0000000..8afdd95 --- /dev/null +++ b/model/product.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass + +@dataclass +class Product: + id: str + name: str + category: str + min_stock: int + optimal_stock: int \ No newline at end of file diff --git a/model/robot.py b/model/robot.py new file mode 100644 index 0000000..fd3847e --- /dev/null +++ b/model/robot.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +@dataclass +class Robot: + id: str + status: str + battery_level: int + last_update: datetime + current_zone: Optional[str] = None + current_row: Optional[int] = None + current_shelf: Optional[int] = None \ No newline at end of file diff --git a/model/user.py b/model/user.py index 03d51fa..6dc216c 100644 --- a/model/user.py +++ b/model/user.py @@ -1,13 +1,16 @@ from dataclasses import dataclass -import json +from datetime import datetime from utils.token import generateKey + @dataclass class User: id: int + email: str + password_hash: str name: str role: str - token: str + created_at: datetime def __init__(self, email: str, passwd: str): #us = getUsModel() #возвращает словарь @@ -18,3 +21,12 @@ class User: def toJson(self): return {"user": {"id": self.id, "name": self.name, "role": self.role}, "token": self.token} + + def is_admin(self) -> bool: + return self.role == 'admin' + + def is_operator(self) -> bool: + return self.role == 'operator' + + def is_viewer(self) -> bool: + return self.role == 'viewer' -- 2.52.0 From 928677fea8f64fd75e6c8637d838e9b966551cda Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:43:32 +0300 Subject: [PATCH 25/40] feat(database): Implemented database connection --- database/__init__.py | 3 ++ database/connection.py | 54 +++++++++++++++++++++++++++++++ database/repositories/__init__.py | 0 3 files changed, 57 insertions(+) create mode 100644 database/__init__.py create mode 100644 database/connection.py create mode 100644 database/repositories/__init__.py diff --git a/database/__init__.py b/database/__init__.py new file mode 100644 index 0000000..c25aeba --- /dev/null +++ b/database/__init__.py @@ -0,0 +1,3 @@ +from .connection import get_connection, get_db_config + +__all__ = ['get_connection', 'get_db_config'] \ No newline at end of file diff --git a/database/connection.py b/database/connection.py new file mode 100644 index 0000000..934624b --- /dev/null +++ b/database/connection.py @@ -0,0 +1,54 @@ +# database/connection.py +import psycopg2 +import os +from contextlib import contextmanager +from typing import Generator + +from utils.loadDotEnv import initializeENV + +initializeENV() + + +def get_db_config() -> dict: + return { + 'host': os.getenv('DB_HOST', 'localhost'), + 'port': int(os.getenv('DB_PORT', 5432)), + 'database': os.getenv('DB_NAME', 'warehouse_db'), + 'user': os.getenv('DB_USER', 'postgres'), + 'password': os.getenv('DB_PASSWORD', '') + } + + +@contextmanager +def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: + conn = None + try: + config = get_db_config() + conn = psycopg2.connect(**config) + print("Подключение к БД установлено") + yield conn + except psycopg2.OperationalError as e: + print(f"Ошибка подключения к БД: {e}") + raise + except Exception as e: + print(f"Неожиданная ошибка: {e}") + if conn: + conn.rollback() + raise + finally: + if conn: + conn.close() + print("БД закрыта") + + +def test_connection() -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT version();") + version = cur.fetchone() + print(f" Версия PostgreSQL: {version[0]}") + return True + except Exception as e: + print(f"Тест подключения бд провален: {e}") + return False diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py new file mode 100644 index 0000000..e69de29 -- 2.52.0 From 3be4556844ce9bc7f34ad9bf24e47909952dc4ae Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 13:14:51 +0300 Subject: [PATCH 26/40] feat(database/repositories/user_repository.py): Added user repository --- database/connection.py | 3 + database/repositories/__init__.py | 3 + database/repositories/user_repository.py | 122 +++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 database/repositories/user_repository.py diff --git a/database/connection.py b/database/connection.py index 934624b..cf575d4 100644 --- a/database/connection.py +++ b/database/connection.py @@ -52,3 +52,6 @@ def test_connection() -> bool: except Exception as e: print(f"Тест подключения бд провален: {e}") return False + + +print(test_connection()) \ No newline at end of file diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index e69de29..24fcbf5 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -0,0 +1,3 @@ +from .user_repository import UserRepository + +__all__ = ['UserRepository'] diff --git a/database/repositories/user_repository.py b/database/repositories/user_repository.py new file mode 100644 index 0000000..35f24a5 --- /dev/null +++ b/database/repositories/user_repository.py @@ -0,0 +1,122 @@ +from typing import List, Optional +from model.user import User +from database.connection import get_connection + + +class UserRepository: + def get_all(self) -> List[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users ORDER BY id") + return [ + User( + id=row[0], + email=row[1], + password_hash=row[2], + name=row[3], + role=row[4], + created_at=row[5] + ) for row in cur.fetchall() + ] + + def get_by_id(self, user_id: int) -> Optional[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + row = cur.fetchone() + if row: + return User(*row) + return None + + def get_by_email(self, email: str) -> Optional[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE email = %s", (email,)) + row = cur.fetchone() + if row: + return User(*row) + return None + + def create_user(self, email: str, password_hash: str, name: str, role: str) -> Optional[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO users (email, password_hash, name, role) + VALUES (%s, %s, %s, %s) + RETURNING id, email, password_hash, name, role, created_at + """, (email, password_hash, name, role)) + + row = cur.fetchone() + conn.commit() + + if row: + return User(*row) + return None + + def update_user(self, user_id: int, name: str = None, role: str = None) -> bool: + with get_connection() as conn: + with conn.cursor() as cur: + updates = [] + params = [] + + if name is not None: + updates.append("name = %s") + params.append(name) + + if role is not None: + updates.append("role = %s") + params.append(role) + + if not updates: + return False + + params.append(user_id) + query = f"UPDATE users SET {', '.join(updates)} WHERE id = %s" + + cur.execute(query, params) + conn.commit() + return cur.rowcount > 0 + + def delete_user(self, user_id: int) -> bool: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM users WHERE id = %s", (user_id,)) + conn.commit() + return cur.rowcount > 0 + + def get_users_by_role(self, role: str) -> List[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE role = %s ORDER BY name", (role,)) + return [ + User(*row) for row in cur.fetchall() + ] + + def change_password(self, user_id: int, new_password_hash: str) -> bool: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + UPDATE users + SET password_hash = %s + WHERE id = %s + """, (new_password_hash, user_id)) + conn.commit() + return cur.rowcount > 0 + + def authenticate_user(self, email: str, password_hash: str) -> Optional[User]: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM users + WHERE email = %s AND password_hash = %s + """, (email, password_hash)) + row = cur.fetchone() + if row: + return User(*row) + return None + + def user_exists(self, email: str) -> bool: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT 1 FROM users WHERE email = %s", (email,)) + return cur.fetchone() is not None \ No newline at end of file -- 2.52.0 From 056d800c4f279151bbb7118c3ae3880a3a575543 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:09:21 +0300 Subject: [PATCH 27/40] feat(database/repositories/robot_repository.py): Added robot_repository --- database/repositories/__init__.py | 3 +- database/repositories/robot_repository.py | 134 ++++++++++++++++++++++ database/repositories/user_repository.py | 1 - 3 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 database/repositories/robot_repository.py diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index 24fcbf5..8bd8ba9 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -1,3 +1,4 @@ from .user_repository import UserRepository +from .robot_repository import RobotRepository -__all__ = ['UserRepository'] +__all__ = ['UserRepository', 'RobotRepository'] diff --git a/database/repositories/robot_repository.py b/database/repositories/robot_repository.py new file mode 100644 index 0000000..904224c --- /dev/null +++ b/database/repositories/robot_repository.py @@ -0,0 +1,134 @@ +# database/repositories/robot_repository.py +from typing import List, Optional +from database.connection import get_connection +from model.robot import Robot +import logging + +class RobotRepository: + def get_all(self) -> List[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM robots ORDER BY id") + return [ + Robot( + id=row[0], + status=row[1], + battery_level=row[2], + last_update=row[3], + current_zone=row[4], + current_row=row[5], + current_shelf=row[6] + ) for row in cur.fetchall() + ] + except Exception as e: + print(f"Ошика получения всех роботов: {e}") + return [] + + def get_by_id(self, robot_id: str) -> Optional[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM robots WHERE id = %s", (robot_id,)) + row = cur.fetchone() + return Robot(*row) if row else None + except Exception as e: + print(f"Ошибка получения роботов {robot_id}: {e}") + return None + + def update_robot(self, robot_id: str, status: str = None, battery_level: int = None, + current_zone: str = None, current_row: int = None, current_shelf: int = None) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + updates = ["last_update = CURRENT_TIMESTAMP"] + params = [] + + if status is not None: + updates.append("status = %s") + params.append(status) + + if battery_level is not None: + updates.append("battery_level = %s") + params.append(battery_level) + + if current_zone is not None: + updates.append("current_zone = %s") + params.append(current_zone) + + if current_row is not None: + updates.append("current_row = %s") + params.append(current_row) + + if current_shelf is not None: + updates.append("current_shelf = %s") + params.append(current_shelf) + + params.append(robot_id) + query = f"UPDATE robots SET {', '.join(updates)} WHERE id = %s" + + cur.execute(query, params) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка обновления робота {robot_id}: {e}") + return False + + def get_robots_by_status(self, status: str) -> List[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM robots WHERE status = %s ORDER BY id", (status,)) + return [Robot(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения роботов по статусу {status}: {e}") + return [] + + def get_low_battery_robots(self, threshold: int = 20) -> List[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM robots + WHERE battery_level < %s AND status = 'active' + ORDER BY battery_level ASC + """, (threshold,)) + return [Robot(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения роботов с низким зарядом: {e}") + return [] + + def get_robots_in_zone(self, zone: str) -> List[Robot]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM robots WHERE current_zone = %s ORDER BY id", (zone,)) + return [Robot(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения роботов в зоне {zone}: {e}") + return [] + + def create_robot(self, robot_id: str, status: str = 'active', battery_level: int = 100) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO robots (id, status, battery_level, last_update) + VALUES (%s, %s, %s, CURRENT_TIMESTAMP) + """, (robot_id, status, battery_level)) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка создания робота {robot_id}: {e}") + return False + + def delete_robot(self, robot_id: str) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM robots WHERE id = %s", (robot_id,)) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка удаления робота {robot_id}: {e}") + return False \ No newline at end of file diff --git a/database/repositories/user_repository.py b/database/repositories/user_repository.py index 35f24a5..d624842 100644 --- a/database/repositories/user_repository.py +++ b/database/repositories/user_repository.py @@ -2,7 +2,6 @@ from typing import List, Optional from model.user import User from database.connection import get_connection - class UserRepository: def get_all(self) -> List[User]: with get_connection() as conn: -- 2.52.0 From 0ded143c556e6f09b42973d8cd99923bf1abd782 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:04:47 +0300 Subject: [PATCH 28/40] feat(database/repositories/product_repository.py): Added product repository --- database/repositories/__init__.py | 3 +- database/repositories/product_repository.py | 139 ++++++++++++++++++++ database/repositories/robot_repository.py | 1 - 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 database/repositories/product_repository.py diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index 8bd8ba9..7ba25ec 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -1,4 +1,5 @@ from .user_repository import UserRepository from .robot_repository import RobotRepository +from .product_repository import ProductRepository -__all__ = ['UserRepository', 'RobotRepository'] +__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository'] diff --git a/database/repositories/product_repository.py b/database/repositories/product_repository.py new file mode 100644 index 0000000..966a4e4 --- /dev/null +++ b/database/repositories/product_repository.py @@ -0,0 +1,139 @@ +# database/repositories/product_repository.py +from typing import List, Optional +from database.connection import get_connection +from model.product import Product + + +class ProductRepository: + def get_all(self) -> List[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM products ORDER BY id") + return [ + Product( + id=row[0], + name=row[1], + category=row[2], + min_stock=row[3], + optimal_stock=row[4] + ) for row in cur.fetchall() + ] + except Exception as e: + print(f"Ошибка получения продуктов: {e}") + return [] + + def get_by_id(self, product_id: str) -> Optional[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM products WHERE id = %s", (product_id,)) + row = cur.fetchone() + return Product(*row) if row else None + except Exception as e: + print(f"Ошибка получения товара {product_id}: {e}") + return None + + def get_by_category(self, category: str) -> List[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM products WHERE category = %s ORDER BY name", (category,)) + return [Product(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения товаров по категории {category}: {e}") + return [] + + def create_product(self, product_id: str, name: str, category: str, + min_stock: int = 10, optimal_stock: int = 100) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO products (id, name, category, min_stock, optimal_stock) + VALUES (%s, %s, %s, %s, %s) + """, (product_id, name, category, min_stock, optimal_stock)) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка создания товара {product_id}: {e}") + return False + + def update_product(self, product_id: str, name: str = None, category: str = None, + min_stock: int = None, optimal_stock: int = None) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + updates = [] + params = [] + + if name is not None: + updates.append("name = %s") + params.append(name) + + if category is not None: + updates.append("category = %s") + params.append(category) + + if min_stock is not None: + updates.append("min_stock = %s") + params.append(min_stock) + + if optimal_stock is not None: + updates.append("optimal_stock = %s") + params.append(optimal_stock) + + if not updates: + return False + + params.append(product_id) + query = f"UPDATE products SET {', '.join(updates)} WHERE id = %s" + + cur.execute(query, params) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка обновления товара {product_id}: {e}") + return False + + def delete_product(self, product_id: str) -> bool: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM products WHERE id = %s", (product_id,)) + conn.commit() + return cur.rowcount > 0 + except Exception as e: + print(f"Ошибка удаления товара {product_id}: {e}") + return False + + def search_products(self, search_term: str) -> List[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM products + WHERE name ILIKE %s OR id ILIKE %s + ORDER BY name + """, (f'%{search_term}%', f'%{search_term}%')) + return [Product(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка поиска товров по названию '{search_term}': {e}") + return [] + + def get_low_stock_products(self) -> List[Product]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT DISTINCT p.* + FROM products p + JOIN inventory_history ih ON p.id = ih.product_id + WHERE ih.status IN ('LOW_STOCK', 'CRITICAL') + AND ih.scanned_at >= NOW() - INTERVAL '1 day' + ORDER BY p.name + """) + return [Product(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения товаров с низким запасом: {e}") + return [] \ No newline at end of file diff --git a/database/repositories/robot_repository.py b/database/repositories/robot_repository.py index 904224c..d57e3f8 100644 --- a/database/repositories/robot_repository.py +++ b/database/repositories/robot_repository.py @@ -2,7 +2,6 @@ from typing import List, Optional from database.connection import get_connection from model.robot import Robot -import logging class RobotRepository: def get_all(self) -> List[Robot]: -- 2.52.0 From 51bf856434c8bc408bb89edab079dea200898781 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:42:20 +0300 Subject: [PATCH 29/40] feat(database/repositories/inventory_repository.py): Added inventory repository --- database/repositories/__init__.py | 3 +- database/repositories/inventory_repository.py | 135 ++++++++++++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 database/repositories/inventory_repository.py diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index 7ba25ec..fc2093e 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -1,5 +1,6 @@ from .user_repository import UserRepository from .robot_repository import RobotRepository from .product_repository import ProductRepository +from .inventory_repository import InventoryRepository -__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository'] +__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository', 'InventoryRepository'] diff --git a/database/repositories/inventory_repository.py b/database/repositories/inventory_repository.py new file mode 100644 index 0000000..bc101b5 --- /dev/null +++ b/database/repositories/inventory_repository.py @@ -0,0 +1,135 @@ +# database/repositories/inventory_repository.py +from typing import List, Optional, Tuple +from datetime import datetime +from database.connection import get_connection +from model.inventory import InventoryRecord + + + +class InventoryRepository: + def get_all(self) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM inventory_history ORDER BY scanned_at DESC") + return [ + InventoryRecord( + id=row[0], + robot_id=row[1], + product_id=row[2], + quantity=row[3], + zone=row[4], + row_number=row[5], + shelf_number=row[6], + status=row[7], + scanned_at=row[8], + created_at=row[9] + ) for row in cur.fetchall() + ] + except Exception as e: + print(f"Ошибка получения истории инвентаризации: {e}") + return [] + + def get_by_id(self, record_id: int) -> Optional[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM inventory_history WHERE id = %s", (record_id,)) + row = cur.fetchone() + return InventoryRecord(*row) if row else None + except Exception as e: + print(f"Ошибка получения записи инвентаризации {record_id}: {e}") + return None + + def create_record(self, robot_id: str, product_id: str, quantity: int, zone: str, + row_number: int, shelf_number: int, status: str, scanned_at: datetime) -> Optional[int]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO inventory_history + (robot_id, product_id, quantity, zone, row_number, shelf_number, status, scanned_at) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + RETURNING id + """, (robot_id, product_id, quantity, zone, row_number, shelf_number, status, scanned_at)) + + record_id = cur.fetchone()[0] + conn.commit() + return record_id + except Exception as e: + print(f"Ошибка создания записи инвентаризации: {e}") + return None + + def get_latest_inventory(self) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT DISTINCT ON (product_id) * + FROM inventory_history + ORDER BY product_id, scanned_at DESC + """) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения последней записи инвентаризации по каждому товару: {e}") + return [] + + def get_records_by_product(self, product_id: str, limit: int = 100) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM inventory_history + WHERE product_id = %s + ORDER BY scanned_at DESC + LIMIT %s + """, (product_id, limit)) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения записи инвентаризации по продукту {product_id}: {e}") + return [] + + def get_records_by_robot(self, robot_id: str, limit: int = 100) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM inventory_history + WHERE robot_id = %s + ORDER BY scanned_at DESC + LIMIT %s + """, (robot_id, limit)) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения записи инвентаризации по роботу {robot_id}: {e}") + return [] + + def get_records_by_zone(self, zone: str, limit: int = 100) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM inventory_history + WHERE zone = %s + ORDER BY scanned_at DESC + LIMIT %s + """, (zone, limit)) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения записи инвентаризации по зоне {zone}: {e}") + return [] + + def get_critical_items(self, hours: int = 24) -> List[InventoryRecord]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM inventory_history + WHERE status IN ('LOW_STOCK', 'CRITICAL') + AND scanned_at >= NOW() - INTERVAL '%s hours' + ORDER BY scanned_at DESC + """, (hours,)) + return [InventoryRecord(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения товаров с критическим статусом: {e}") + return [] -- 2.52.0 From 88c4460da9fea7e547d5bf6d46fef604ad3e9d44 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:52:48 +0300 Subject: [PATCH 30/40] feat(model/ai_prediction.py): Added new model AIPrediction --- model/__init__.py | 3 ++- model/ai_prediction.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 model/ai_prediction.py diff --git a/model/__init__.py b/model/__init__.py index 5e9673e..988d60d 100644 --- a/model/__init__.py +++ b/model/__init__.py @@ -2,5 +2,6 @@ from .robot import Robot from .product import Product from .inventory import InventoryRecord from .user import User +from .ai_prediction import AIPrediction -__all__ = ['Robot', 'Product', 'InventoryRecord', 'User'] \ No newline at end of file +__all__ = ['Robot', 'Product', 'InventoryRecord', 'User', 'AIPrediction'] \ No newline at end of file diff --git a/model/ai_prediction.py b/model/ai_prediction.py new file mode 100644 index 0000000..1685105 --- /dev/null +++ b/model/ai_prediction.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass +from datetime import datetime, date + +@dataclass +class AIPrediction: + id: int + product_id: str + prediction_date: date + days_until_stockout: int + recommended_order: int + confidence_score: float + created_at: datetime \ No newline at end of file -- 2.52.0 From fd1b7df93a438e5fc190a835500cd1e45851d3f2 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 16:09:33 +0300 Subject: [PATCH 31/40] feat(database/repositories/ai_prediction_repository.py): Added new repository AIPredictionsRepository --- database/repositories/__init__.py | 3 +- .../repositories/ai_prediction_repository.py | 102 ++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 database/repositories/ai_prediction_repository.py diff --git a/database/repositories/__init__.py b/database/repositories/__init__.py index fc2093e..b1a1393 100644 --- a/database/repositories/__init__.py +++ b/database/repositories/__init__.py @@ -2,5 +2,6 @@ from .user_repository import UserRepository from .robot_repository import RobotRepository from .product_repository import ProductRepository from .inventory_repository import InventoryRepository +from .ai_prediction_repository import AIPredictionsRepository -__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository', 'InventoryRepository'] +__all__ = ['UserRepository', 'RobotRepository', 'ProductRepository', 'InventoryRepository', 'AIPredictionsRepository'] diff --git a/database/repositories/ai_prediction_repository.py b/database/repositories/ai_prediction_repository.py new file mode 100644 index 0000000..31788c6 --- /dev/null +++ b/database/repositories/ai_prediction_repository.py @@ -0,0 +1,102 @@ +from typing import List, Optional +from datetime import datetime, date +from database.connection import get_connection +from model.ai_prediction import AIPrediction + + +class AIPredictionsRepository: + def get_all(self) -> List[AIPrediction]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM ai_predictions ORDER BY prediction_date DESC, product_id") + return [ + AIPrediction( + id=row[0], + product_id=row[1], + prediction_date=row[2], + days_until_stockout=row[3], + recommended_order=row[4], + confidence_score=row[5], + created_at=row[6] + ) for row in cur.fetchall() + ] + except Exception as e: + print(f"Ошибка получения прогнозов: {e}") + return [] + + def get_by_id(self, prediction_id: int) -> Optional[AIPrediction]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM ai_predictions WHERE id = %s", (prediction_id,)) + row = cur.fetchone() + return AIPrediction(*row) if row else None + except Exception as e: + print(f"Ошибка получения прогноза {prediction_id}: {e}") + return None + + def get_by_product(self, product_id: str, limit: int = 10) -> List[AIPrediction]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM ai_predictions + WHERE product_id = %s + ORDER BY prediction_date DESC + LIMIT %s + """, (product_id, limit)) + return [AIPrediction(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения прогноза по товару {product_id}: {e}") + return [] + + def get_latest_predictions(self) -> List[AIPrediction]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT DISTINCT ON (product_id) * + FROM ai_predictions + ORDER BY product_id, prediction_date DESC + """) + return [AIPrediction(*row) for row in cur.fetchall()] + except Exception as e: + print(f"Ошибка получения последних прогнозов по товарам: {e}") + return [] + + def create_prediction(self, product_id: str, prediction_date: date, + days_until_stockout: int, recommended_order: int, + confidence_score: float) -> Optional[int]: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO ai_predictions + (product_id, prediction_date, days_until_stockout, recommended_order, confidence_score) + VALUES (%s, %s, %s, %s, %s) + RETURNING id + """, (product_id, prediction_date, days_until_stockout, recommended_order, confidence_score)) + + prediction_id = cur.fetchone()[0] + conn.commit() + return prediction_id + except Exception as e: + print(f"Ошибка создания прогноза: {e}") + return None + + + def delete_old_predictions(self, older_than_days: int = 90) -> int: + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + DELETE FROM ai_predictions + WHERE prediction_date < CURRENT_DATE - INTERVAL '%s days' + """, (older_than_days,)) + deleted_count = cur.rowcount + conn.commit() + return deleted_count + except Exception as e: + print(f"Ошибка удаления старых прогнозов: {e}") + return 0 \ No newline at end of file -- 2.52.0 From 771f7e05493377dd2bb7459de88b87e46dc0422a Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 16:32:02 +0300 Subject: [PATCH 32/40] feat(database/repositories/user_repository.py): A new login verification method has been added --- database/repositories/user_repository.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/database/repositories/user_repository.py b/database/repositories/user_repository.py index d624842..22bb523 100644 --- a/database/repositories/user_repository.py +++ b/database/repositories/user_repository.py @@ -114,6 +114,15 @@ class UserRepository: return User(*row) return None + def is_valid_authenticate(self, email: str, password_hash: str) -> bool: + 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)) + return cur.fetchone() is not None + def user_exists(self, email: str) -> bool: with get_connection() as conn: with conn.cursor() as cur: -- 2.52.0 From 6006e074d8f5c890dd45d954d4fa6bd45f12c399 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 18:48:54 +0300 Subject: [PATCH 33/40] fix(model/user.py): Removed unnecesary methods --- model/user.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/model/user.py b/model/user.py index 6dc216c..d98c9c7 100644 --- a/model/user.py +++ b/model/user.py @@ -21,12 +21,3 @@ class User: def toJson(self): return {"user": {"id": self.id, "name": self.name, "role": self.role}, "token": self.token} - - def is_admin(self) -> bool: - return self.role == 'admin' - - def is_operator(self) -> bool: - return self.role == 'operator' - - def is_viewer(self) -> bool: - return self.role == 'viewer' -- 2.52.0 From 33c3b81f6ea7c82c3441b03a164abb8ef5aa9308 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 18:52:54 +0300 Subject: [PATCH 34/40] style(database): Refactor repository name --- database/__init__.py | 3 --- {database => db}/connection.py | 4 ++-- {database => db}/repositories/__init__.py | 0 {database => db}/repositories/ai_prediction_repository.py | 2 +- {database => db}/repositories/inventory_repository.py | 4 ++-- {database => db}/repositories/product_repository.py | 4 ++-- {database => db}/repositories/robot_repository.py | 4 ++-- {database => db}/repositories/user_repository.py | 2 +- 8 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 database/__init__.py rename {database => db}/connection.py (94%) rename {database => db}/repositories/__init__.py (100%) rename {database => db}/repositories/ai_prediction_repository.py (98%) rename {database => db}/repositories/inventory_repository.py (98%) rename {database => db}/repositories/product_repository.py (98%) rename {database => db}/repositories/robot_repository.py (98%) rename {database => db}/repositories/user_repository.py (99%) diff --git a/database/__init__.py b/database/__init__.py deleted file mode 100644 index c25aeba..0000000 --- a/database/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .connection import get_connection, get_db_config - -__all__ = ['get_connection', 'get_db_config'] \ No newline at end of file diff --git a/database/connection.py b/db/connection.py similarity index 94% rename from database/connection.py rename to db/connection.py index cf575d4..d1391fe 100644 --- a/database/connection.py +++ b/db/connection.py @@ -1,4 +1,4 @@ -# database/connection.py +# db/connection.py import psycopg2 import os from contextlib import contextmanager @@ -13,7 +13,7 @@ def get_db_config() -> dict: return { 'host': os.getenv('DB_HOST', 'localhost'), 'port': int(os.getenv('DB_PORT', 5432)), - 'database': os.getenv('DB_NAME', 'warehouse_db'), + 'db': os.getenv('DB_NAME', 'warehouse_db'), 'user': os.getenv('DB_USER', 'postgres'), 'password': os.getenv('DB_PASSWORD', '') } diff --git a/database/repositories/__init__.py b/db/repositories/__init__.py similarity index 100% rename from database/repositories/__init__.py rename to db/repositories/__init__.py diff --git a/database/repositories/ai_prediction_repository.py b/db/repositories/ai_prediction_repository.py similarity index 98% rename from database/repositories/ai_prediction_repository.py rename to db/repositories/ai_prediction_repository.py index 31788c6..3d3b430 100644 --- a/database/repositories/ai_prediction_repository.py +++ b/db/repositories/ai_prediction_repository.py @@ -1,6 +1,6 @@ from typing import List, Optional from datetime import datetime, date -from database.connection import get_connection +from db.connection import get_connection from model.ai_prediction import AIPrediction diff --git a/database/repositories/inventory_repository.py b/db/repositories/inventory_repository.py similarity index 98% rename from database/repositories/inventory_repository.py rename to db/repositories/inventory_repository.py index bc101b5..4ba33fd 100644 --- a/database/repositories/inventory_repository.py +++ b/db/repositories/inventory_repository.py @@ -1,7 +1,7 @@ -# database/repositories/inventory_repository.py +# db/repositories/inventory_repository.py from typing import List, Optional, Tuple from datetime import datetime -from database.connection import get_connection +from db.connection import get_connection from model.inventory import InventoryRecord diff --git a/database/repositories/product_repository.py b/db/repositories/product_repository.py similarity index 98% rename from database/repositories/product_repository.py rename to db/repositories/product_repository.py index 966a4e4..8d55ce2 100644 --- a/database/repositories/product_repository.py +++ b/db/repositories/product_repository.py @@ -1,6 +1,6 @@ -# database/repositories/product_repository.py +# db/repositories/product_repository.py from typing import List, Optional -from database.connection import get_connection +from db.connection import get_connection from model.product import Product diff --git a/database/repositories/robot_repository.py b/db/repositories/robot_repository.py similarity index 98% rename from database/repositories/robot_repository.py rename to db/repositories/robot_repository.py index d57e3f8..a259e34 100644 --- a/database/repositories/robot_repository.py +++ b/db/repositories/robot_repository.py @@ -1,6 +1,6 @@ -# database/repositories/robot_repository.py +# db/repositories/robot_repository.py from typing import List, Optional -from database.connection import get_connection +from db.connection import get_connection from model.robot import Robot class RobotRepository: diff --git a/database/repositories/user_repository.py b/db/repositories/user_repository.py similarity index 99% rename from database/repositories/user_repository.py rename to db/repositories/user_repository.py index 22bb523..7b1d979 100644 --- a/database/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -1,6 +1,6 @@ from typing import List, Optional from model.user import User -from database.connection import get_connection +from db.connection import get_connection class UserRepository: def get_all(self) -> List[User]: -- 2.52.0 From 7786680f43d6331c4d74ab12272308716c5a3d0d Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 19:14:51 +0300 Subject: [PATCH 35/40] fix(database): Fixed the database connection --- db/connection.py | 36 +++++++++++++++--------------------- utils/PostgressConnect.py | 10 ---------- 2 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 utils/PostgressConnect.py diff --git a/db/connection.py b/db/connection.py index d1391fe..4d65310 100644 --- a/db/connection.py +++ b/db/connection.py @@ -8,23 +8,20 @@ from utils.loadDotEnv import initializeENV initializeENV() +def PSQLConnect(): + conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) + return conn -def get_db_config() -> dict: - return { - 'host': os.getenv('DB_HOST', 'localhost'), - 'port': int(os.getenv('DB_PORT', 5432)), - 'db': os.getenv('DB_NAME', 'warehouse_db'), - 'user': os.getenv('DB_USER', 'postgres'), - 'password': os.getenv('DB_PASSWORD', '') - } +def PSQLCursor(conn): + cur = conn.cursor() + return cur @contextmanager def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: conn = None try: - config = get_db_config() - conn = psycopg2.connect(**config) + conn = PSQLConnect() print("Подключение к БД установлено") yield conn except psycopg2.OperationalError as e: @@ -32,26 +29,23 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: raise except Exception as e: print(f"Неожиданная ошибка: {e}") - if conn: - conn.rollback() raise finally: if conn: conn.close() - print("БД закрыта") - + print("Соединение с БД закрыто") def test_connection() -> bool: try: with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT version();") - version = cur.fetchone() - print(f" Версия PostgreSQL: {version[0]}") - return True + cur = PSQLCursor(conn) + cur.execute("SELECT version();") + version = cur.fetchone() + print(f"Версия PostgreSQL: {version[0]}") + cur.close() + return True except Exception as e: - print(f"Тест подключения бд провален: {e}") + print(f"Тест подключения к БД провален: {e}") return False - print(test_connection()) \ No newline at end of file diff --git a/utils/PostgressConnect.py b/utils/PostgressConnect.py deleted file mode 100644 index ea6a485..0000000 --- a/utils/PostgressConnect.py +++ /dev/null @@ -1,10 +0,0 @@ -import psycopg2 -import os - -def PSQLConnect(): - conn = psycopg2.connect(os.getenv('POSTGRES_URL')) - return conn - -def PSQLCursor(conn): - cur = conn.cursor() - return cur -- 2.52.0 From 951e4c7e45b4449a1239dd2987f94d287bf970d9 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 19:32:34 +0300 Subject: [PATCH 36/40] style(database): Changed logging --- db/connection.py | 36 ++- db/repositories/ai_prediction_repository.py | 37 ++- db/repositories/inventory_repository.py | 50 ++-- db/repositories/product_repository.py | 63 +++-- db/repositories/robot_repository.py | 62 +++-- db/repositories/user_repository.py | 277 +++++++++++++------- model/ai_prediction.py | 2 +- model/inventory.py | 2 +- model/product.py | 2 +- model/robot.py | 2 +- 10 files changed, 358 insertions(+), 175 deletions(-) diff --git a/db/connection.py b/db/connection.py index 4d65310..3950913 100644 --- a/db/connection.py +++ b/db/connection.py @@ -1,6 +1,6 @@ -# db/connection.py import psycopg2 import os +import logging from contextlib import contextmanager from typing import Generator @@ -8,6 +8,8 @@ from utils.loadDotEnv import initializeENV initializeENV() +logger = logging.getLogger(__name__) + def PSQLConnect(): conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) return conn @@ -22,30 +24,38 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: conn = None try: conn = PSQLConnect() - print("Подключение к БД установлено") + logger.debug("Подключение к БД установлено") yield conn except psycopg2.OperationalError as e: - print(f"Ошибка подключения к БД: {e}") + logger.error(f"Ошибка подключения к БД: {e}") + raise + except psycopg2.Error as e: + logger.error(f"Ошибка PostgreSQL: {e}") raise except Exception as e: - print(f"Неожиданная ошибка: {e}") + logger.error(f"Неожиданная ошибка при работе с БД: {e}") raise finally: if conn: - conn.close() - print("Соединение с БД закрыто") + try: + conn.close() + logger.debug("Соединение с БД закрыто") + except Exception as e: + logger.warning(f"Ошибка при закрытии соединения: {e}") def test_connection() -> bool: try: with get_connection() as conn: cur = PSQLCursor(conn) - cur.execute("SELECT version();") - version = cur.fetchone() - print(f"Версия PostgreSQL: {version[0]}") - cur.close() - return True + try: + cur.execute("SELECT version();") + version = cur.fetchone() + logger.debug(f"Версия PostgreSQL: {version[0]}") + return True + finally: + cur.close() + logger.debug("Курсор закрыт") except Exception as e: - print(f"Тест подключения к БД провален: {e}") + logger.error(f"Тест подключения к БД провален: {e}") return False - print(test_connection()) \ No newline at end of file diff --git a/db/repositories/ai_prediction_repository.py b/db/repositories/ai_prediction_repository.py index 3d3b430..8c6251d 100644 --- a/db/repositories/ai_prediction_repository.py +++ b/db/repositories/ai_prediction_repository.py @@ -1,8 +1,10 @@ from typing import List, Optional from datetime import datetime, date +import logging from db.connection import get_connection from model.ai_prediction import AIPrediction +logger = logging.getLogger(__name__) class AIPredictionsRepository: def get_all(self) -> List[AIPrediction]: @@ -10,7 +12,7 @@ class AIPredictionsRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM ai_predictions ORDER BY prediction_date DESC, product_id") - return [ + predictions = [ AIPrediction( id=row[0], product_id=row[1], @@ -21,8 +23,10 @@ class AIPredictionsRepository: created_at=row[6] ) for row in cur.fetchall() ] + logger.debug(f"Получено {len(predictions)} прогнозов") + return predictions except Exception as e: - print(f"Ошибка получения прогнозов: {e}") + logger.error(f"Ошибка получения прогнозов: {e}") return [] def get_by_id(self, prediction_id: int) -> Optional[AIPrediction]: @@ -31,9 +35,13 @@ class AIPredictionsRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM ai_predictions WHERE id = %s", (prediction_id,)) row = cur.fetchone() - return AIPrediction(*row) if row else None + if row: + logger.debug(f"Прогноз {prediction_id} найден") + return AIPrediction(*row) + logger.warning(f"Прогноз {prediction_id} не найден") + return None except Exception as e: - print(f"Ошибка получения прогноза {prediction_id}: {e}") + logger.error(f"Ошибка получения прогноза {prediction_id}: {e}") return None def get_by_product(self, product_id: str, limit: int = 10) -> List[AIPrediction]: @@ -46,9 +54,11 @@ class AIPredictionsRepository: ORDER BY prediction_date DESC LIMIT %s """, (product_id, limit)) - return [AIPrediction(*row) for row in cur.fetchall()] + predictions = [AIPrediction(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(predictions)} прогнозов для товара {product_id}") + return predictions except Exception as e: - print(f"Ошибка получения прогноза по товару {product_id}: {e}") + logger.error(f"Ошибка получения прогноза по товару {product_id}: {e}") return [] def get_latest_predictions(self) -> List[AIPrediction]: @@ -60,9 +70,11 @@ class AIPredictionsRepository: FROM ai_predictions ORDER BY product_id, prediction_date DESC """) - return [AIPrediction(*row) for row in cur.fetchall()] + predictions = [AIPrediction(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(predictions)} последних прогнозов по товарам") + return predictions except Exception as e: - print(f"Ошибка получения последних прогнозов по товарам: {e}") + logger.error(f"Ошибка получения последних прогнозов по товарам: {e}") return [] def create_prediction(self, product_id: str, prediction_date: date, @@ -80,12 +92,12 @@ class AIPredictionsRepository: prediction_id = cur.fetchone()[0] conn.commit() + logger.debug(f"Создан новый прогноз ID: {prediction_id} для товара {product_id}") return prediction_id except Exception as e: - print(f"Ошибка создания прогноза: {e}") + logger.error(f"Ошибка создания прогноза: {e}") return None - def delete_old_predictions(self, older_than_days: int = 90) -> int: try: with get_connection() as conn: @@ -96,7 +108,8 @@ class AIPredictionsRepository: """, (older_than_days,)) deleted_count = cur.rowcount conn.commit() + logger.debug(f"Удалено {deleted_count} старых прогнозов старше {older_than_days} дней") return deleted_count except Exception as e: - print(f"Ошибка удаления старых прогнозов: {e}") - return 0 \ No newline at end of file + logger.error(f"Ошибка удаления старых прогнозов: {e}") + return 0 diff --git a/db/repositories/inventory_repository.py b/db/repositories/inventory_repository.py index 4ba33fd..5bcbc31 100644 --- a/db/repositories/inventory_repository.py +++ b/db/repositories/inventory_repository.py @@ -1,10 +1,11 @@ # db/repositories/inventory_repository.py from typing import List, Optional, Tuple from datetime import datetime +import logging from db.connection import get_connection from model.inventory import InventoryRecord - +logger = logging.getLogger(__name__) class InventoryRepository: def get_all(self) -> List[InventoryRecord]: @@ -12,7 +13,7 @@ class InventoryRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM inventory_history ORDER BY scanned_at DESC") - return [ + records = [ InventoryRecord( id=row[0], robot_id=row[1], @@ -26,8 +27,10 @@ class InventoryRepository: created_at=row[9] ) for row in cur.fetchall() ] + logger.debug(f"Получено {len(records)} записей инвентаризации") + return records except Exception as e: - print(f"Ошибка получения истории инвентаризации: {e}") + logger.error(f"Ошибка получения истории инвентаризации: {e}") return [] def get_by_id(self, record_id: int) -> Optional[InventoryRecord]: @@ -36,9 +39,13 @@ class InventoryRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM inventory_history WHERE id = %s", (record_id,)) row = cur.fetchone() - return InventoryRecord(*row) if row else None + if row: + logger.debug(f"Запись инвентаризации {record_id} найдена") + return InventoryRecord(*row) + logger.warning(f"Запись инвентаризации {record_id} не найдена") + return None except Exception as e: - print(f"Ошибка получения записи инвентаризации {record_id}: {e}") + logger.error(f"Ошибка получения записи инвентаризации {record_id}: {e}") return None def create_record(self, robot_id: str, product_id: str, quantity: int, zone: str, @@ -55,9 +62,10 @@ class InventoryRepository: record_id = cur.fetchone()[0] conn.commit() + logger.debug(f"Создана новая запись инвентаризации ID: {record_id}") return record_id except Exception as e: - print(f"Ошибка создания записи инвентаризации: {e}") + logger.error(f"Ошибка создания записи инвентаризации: {e}") return None def get_latest_inventory(self) -> List[InventoryRecord]: @@ -69,9 +77,11 @@ class InventoryRepository: FROM inventory_history ORDER BY product_id, scanned_at DESC """) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(records)} последних записей инвентаризации по товарам") + return records except Exception as e: - print(f"Ошибка получения последней записи инвентаризации по каждому товару: {e}") + logger.error(f"Ошибка получения последней записи инвентаризации по каждому товару: {e}") return [] def get_records_by_product(self, product_id: str, limit: int = 100) -> List[InventoryRecord]: @@ -84,9 +94,11 @@ class InventoryRepository: ORDER BY scanned_at DESC LIMIT %s """, (product_id, limit)) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(records)} записей инвентаризации для товара {product_id}") + return records except Exception as e: - print(f"Ошибка получения записи инвентаризации по продукту {product_id}: {e}") + logger.error(f"Ошибка получения записи инвентаризации по продукту {product_id}: {e}") return [] def get_records_by_robot(self, robot_id: str, limit: int = 100) -> List[InventoryRecord]: @@ -99,9 +111,11 @@ class InventoryRepository: ORDER BY scanned_at DESC LIMIT %s """, (robot_id, limit)) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(records)} записей инвентаризации для робота {robot_id}") + return records except Exception as e: - print(f"Ошибка получения записи инвентаризации по роботу {robot_id}: {e}") + logger.error(f"Ошибка получения записи инвентаризации по роботу {robot_id}: {e}") return [] def get_records_by_zone(self, zone: str, limit: int = 100) -> List[InventoryRecord]: @@ -114,9 +128,11 @@ class InventoryRepository: ORDER BY scanned_at DESC LIMIT %s """, (zone, limit)) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(records)} записей инвентаризации для зоны {zone}") + return records except Exception as e: - print(f"Ошибка получения записи инвентаризации по зоне {zone}: {e}") + logger.error(f"Ошибка получения записи инвентаризации по зоне {zone}: {e}") return [] def get_critical_items(self, hours: int = 24) -> List[InventoryRecord]: @@ -129,7 +145,9 @@ class InventoryRepository: AND scanned_at >= NOW() - INTERVAL '%s hours' ORDER BY scanned_at DESC """, (hours,)) - return [InventoryRecord(*row) for row in cur.fetchall()] + records = [InventoryRecord(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(records)} критических товаров за последние {hours} часов") + return records except Exception as e: - print(f"Ошибка получения товаров с критическим статусом: {e}") + logger.error(f"Ошибка получения товаров с критическим статусом: {e}") return [] diff --git a/db/repositories/product_repository.py b/db/repositories/product_repository.py index 8d55ce2..6de73b8 100644 --- a/db/repositories/product_repository.py +++ b/db/repositories/product_repository.py @@ -1,8 +1,9 @@ -# db/repositories/product_repository.py from typing import List, Optional +import logging from db.connection import get_connection from model.product import Product +logger = logging.getLogger(__name__) class ProductRepository: def get_all(self) -> List[Product]: @@ -10,7 +11,7 @@ class ProductRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM products ORDER BY id") - return [ + products = [ Product( id=row[0], name=row[1], @@ -19,8 +20,10 @@ class ProductRepository: optimal_stock=row[4] ) for row in cur.fetchall() ] + logger.debug(f"Получено {len(products)} товаров") + return products except Exception as e: - print(f"Ошибка получения продуктов: {e}") + logger.error(f"Ошибка получения продуктов: {e}") return [] def get_by_id(self, product_id: str) -> Optional[Product]: @@ -29,9 +32,13 @@ class ProductRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM products WHERE id = %s", (product_id,)) row = cur.fetchone() - return Product(*row) if row else None + if row: + logger.debug(f"Товар {product_id} найден") + return Product(*row) + logger.warning(f"Товар {product_id} не найден") + return None except Exception as e: - print(f"Ошибка получения товара {product_id}: {e}") + logger.error(f"Ошибка получения товара {product_id}: {e}") return None def get_by_category(self, category: str) -> List[Product]: @@ -39,9 +46,11 @@ class ProductRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM products WHERE category = %s ORDER BY name", (category,)) - return [Product(*row) for row in cur.fetchall()] + products = [Product(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(products)} товаров категории {category}") + return products except Exception as e: - print(f"Ошибка получения товаров по категории {category}: {e}") + logger.error(f"Ошибка получения товаров по категории {category}: {e}") return [] def create_product(self, product_id: str, name: str, category: str, @@ -54,9 +63,14 @@ class ProductRepository: VALUES (%s, %s, %s, %s, %s) """, (product_id, name, category, min_stock, optimal_stock)) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.debug(f"Создан новый товар {product_id}: {name}") + else: + logger.warning(f"Не удалось создать товар {product_id}") + return success except Exception as e: - print(f"Ошибка создания товара {product_id}: {e}") + logger.error(f"Ошибка создания товара {product_id}: {e}") return False def update_product(self, product_id: str, name: str = None, category: str = None, @@ -84,6 +98,7 @@ class ProductRepository: params.append(optimal_stock) if not updates: + logger.warning(f"Нет данных для обновления товара {product_id}") return False params.append(product_id) @@ -91,9 +106,14 @@ class ProductRepository: cur.execute(query, params) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.debug(f"Товар {product_id} успешно обновлен") + else: + logger.warning(f"Товар {product_id} не найден для обновления") + return success except Exception as e: - print(f"Ошибка обновления товара {product_id}: {e}") + logger.error(f"Ошибка обновления товара {product_id}: {e}") return False def delete_product(self, product_id: str) -> bool: @@ -102,9 +122,14 @@ class ProductRepository: with conn.cursor() as cur: cur.execute("DELETE FROM products WHERE id = %s", (product_id,)) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.debug(f"Товар {product_id} удален") + else: + logger.warning(f"Товар {product_id} не найден для удаления") + return success except Exception as e: - print(f"Ошибка удаления товара {product_id}: {e}") + logger.error(f"Ошибка удаления товара {product_id}: {e}") return False def search_products(self, search_term: str) -> List[Product]: @@ -116,9 +141,11 @@ class ProductRepository: WHERE name ILIKE %s OR id ILIKE %s ORDER BY name """, (f'%{search_term}%', f'%{search_term}%')) - return [Product(*row) for row in cur.fetchall()] + products = [Product(*row) for row in cur.fetchall()] + logger.debug(f"Найдено {len(products)} товаров по запросу '{search_term}'") + return products except Exception as e: - print(f"Ошибка поиска товров по названию '{search_term}': {e}") + logger.error(f"Ошибка поиска товаров по названию '{search_term}': {e}") return [] def get_low_stock_products(self) -> List[Product]: @@ -133,7 +160,9 @@ class ProductRepository: AND ih.scanned_at >= NOW() - INTERVAL '1 day' ORDER BY p.name """) - return [Product(*row) for row in cur.fetchall()] + products = [Product(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(products)} товаров с низким запасом") + return products except Exception as e: - print(f"Ошибка получения товаров с низким запасом: {e}") + logger.error(f"Ошибка получения товаров с низким запасом: {e}") return [] \ No newline at end of file diff --git a/db/repositories/robot_repository.py b/db/repositories/robot_repository.py index a259e34..74879b9 100644 --- a/db/repositories/robot_repository.py +++ b/db/repositories/robot_repository.py @@ -1,15 +1,18 @@ # db/repositories/robot_repository.py from typing import List, Optional +import logging from db.connection import get_connection from model.robot import Robot +logger = logging.getLogger(__name__) + class RobotRepository: def get_all(self) -> List[Robot]: try: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM robots ORDER BY id") - return [ + robots = [ Robot( id=row[0], status=row[1], @@ -20,8 +23,10 @@ class RobotRepository: current_shelf=row[6] ) for row in cur.fetchall() ] + logger.debug(f"Получено {len(robots)} роботов") + return robots except Exception as e: - print(f"Ошика получения всех роботов: {e}") + logger.error(f"Ошибка получения всех роботов: {e}") return [] def get_by_id(self, robot_id: str) -> Optional[Robot]: @@ -30,9 +35,13 @@ class RobotRepository: with conn.cursor() as cur: cur.execute("SELECT * FROM robots WHERE id = %s", (robot_id,)) row = cur.fetchone() - return Robot(*row) if row else None + if row: + logger.debug(f"Робот {robot_id} найден") + return Robot(*row) + logger.warning(f"Робот {robot_id} не найден") + return None except Exception as e: - print(f"Ошибка получения роботов {robot_id}: {e}") + logger.error(f"Ошибка получения робота {robot_id}: {e}") return None def update_robot(self, robot_id: str, status: str = None, battery_level: int = None, @@ -68,9 +77,14 @@ class RobotRepository: cur.execute(query, params) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.debug(f"Робот {robot_id} успешно обновлен") + else: + logger.warning(f"Робот {robot_id} не найден для обновления") + return success except Exception as e: - print(f"Ошибка обновления робота {robot_id}: {e}") + logger.error(f"Ошибка обновления робота {robot_id}: {e}") return False def get_robots_by_status(self, status: str) -> List[Robot]: @@ -78,9 +92,11 @@ class RobotRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM robots WHERE status = %s ORDER BY id", (status,)) - return [Robot(*row) for row in cur.fetchall()] + robots = [Robot(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(robots)} роботов со статусом {status}") + return robots except Exception as e: - print(f"Ошибка получения роботов по статусу {status}: {e}") + logger.error(f"Ошибка получения роботов по статусу {status}: {e}") return [] def get_low_battery_robots(self, threshold: int = 20) -> List[Robot]: @@ -92,9 +108,11 @@ class RobotRepository: WHERE battery_level < %s AND status = 'active' ORDER BY battery_level ASC """, (threshold,)) - return [Robot(*row) for row in cur.fetchall()] + robots = [Robot(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(robots)} роботов с низким зарядом (<{threshold}%)") + return robots except Exception as e: - print(f"Ошибка получения роботов с низким зарядом: {e}") + logger.error(f"Ошибка получения роботов с низким зарядом: {e}") return [] def get_robots_in_zone(self, zone: str) -> List[Robot]: @@ -102,9 +120,11 @@ class RobotRepository: with get_connection() as conn: with conn.cursor() as cur: cur.execute("SELECT * FROM robots WHERE current_zone = %s ORDER BY id", (zone,)) - return [Robot(*row) for row in cur.fetchall()] + robots = [Robot(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(robots)} роботов в зоне {zone}") + return robots except Exception as e: - print(f"Ошибка получения роботов в зоне {zone}: {e}") + logger.error(f"Ошибка получения роботов в зоне {zone}: {e}") return [] def create_robot(self, robot_id: str, status: str = 'active', battery_level: int = 100) -> bool: @@ -116,9 +136,14 @@ class RobotRepository: VALUES (%s, %s, %s, CURRENT_TIMESTAMP) """, (robot_id, status, battery_level)) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.debug(f"Создан новый робот {robot_id}") + else: + logger.warning(f"Не удалось создать робота {robot_id}") + return success except Exception as e: - print(f"Ошибка создания робота {robot_id}: {e}") + logger.error(f"Ошибка создания робота {robot_id}: {e}") return False def delete_robot(self, robot_id: str) -> bool: @@ -127,7 +152,12 @@ class RobotRepository: with conn.cursor() as cur: cur.execute("DELETE FROM robots WHERE id = %s", (robot_id,)) conn.commit() - return cur.rowcount > 0 + success = cur.rowcount > 0 + if success: + logger.debug(f"Робот {robot_id} удален") + else: + logger.warning(f"Робот {robot_id} не найден для удаления") + return success except Exception as e: - print(f"Ошибка удаления робота {robot_id}: {e}") + logger.error(f"Ошибка удаления робота {robot_id}: {e}") return False \ No newline at end of file diff --git a/db/repositories/user_repository.py b/db/repositories/user_repository.py index 7b1d979..d3a2a11 100644 --- a/db/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -1,130 +1,213 @@ from typing import List, Optional +import logging from model.user import User from db.connection import get_connection +logger = logging.getLogger(__name__) + class UserRepository: def get_all(self) -> List[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT * FROM users ORDER BY id") - return [ - User( - id=row[0], - email=row[1], - password_hash=row[2], - name=row[3], - role=row[4], - created_at=row[5] - ) for row in cur.fetchall() - ] + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users ORDER BY id") + users = [ + User( + id=row[0], + email=row[1], + password_hash=row[2], + name=row[3], + role=row[4], + created_at=row[5] + ) for row in cur.fetchall() + ] + logger.debug(f"Получено {len(users)} пользователей") + return users + except Exception as e: + logger.error(f"Ошибка получения пользователей: {e}") + return [] def get_by_id(self, user_id: int) -> Optional[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT * FROM users WHERE id = %s", (user_id,)) - row = cur.fetchone() - if row: - return User(*row) - return None + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE id = %s", (user_id,)) + row = cur.fetchone() + if row: + logger.debug(f"Пользователь {user_id} найден") + return User(*row) + logger.warning(f"Пользователь {user_id} не найден") + return None + except Exception as e: + logger.error(f"Ошибка получения пользователя {user_id}: {e}") + return None def get_by_email(self, email: str) -> Optional[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT * FROM users WHERE email = %s", (email,)) - row = cur.fetchone() - if row: - return User(*row) - return None + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE email = %s", (email,)) + row = cur.fetchone() + if row: + logger.debug(f"Пользователь с email {email} найден") + return User(*row) + logger.warning(f"Пользователь с email {email} не найден") + return None + except Exception as e: + logger.error(f"Ошибка получения пользователя по email {email}: {e}") + return None def create_user(self, email: str, password_hash: str, name: str, role: str) -> Optional[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute(""" - INSERT INTO users (email, password_hash, name, role) - VALUES (%s, %s, %s, %s) - RETURNING id, email, password_hash, name, role, created_at - """, (email, password_hash, name, role)) + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO users (email, password_hash, name, role) + VALUES (%s, %s, %s, %s) + RETURNING id, email, password_hash, name, role, created_at + """, (email, password_hash, name, role)) - row = cur.fetchone() - conn.commit() + row = cur.fetchone() + conn.commit() - if row: - return User(*row) - return None + if row: + logger.debug(f"Создан новый пользователь {email} с ID {row[0]}") + return User(*row) + logger.warning(f"Не удалось создать пользователя {email}") + return None + except Exception as e: + logger.error(f"Ошибка создания пользователя {email}: {e}") + return None def update_user(self, user_id: int, name: str = None, role: str = None) -> bool: - with get_connection() as conn: - with conn.cursor() as cur: - updates = [] - params = [] + try: + with get_connection() as conn: + with conn.cursor() as cur: + updates = [] + params = [] - if name is not None: - updates.append("name = %s") - params.append(name) + if name is not None: + updates.append("name = %s") + params.append(name) - if role is not None: - updates.append("role = %s") - params.append(role) + if role is not None: + updates.append("role = %s") + params.append(role) - if not updates: - return False + if not updates: + logger.warning(f"Нет данных для обновления пользователя {user_id}") + return False - params.append(user_id) - query = f"UPDATE users SET {', '.join(updates)} WHERE id = %s" + params.append(user_id) + query = f"UPDATE users SET {', '.join(updates)} WHERE id = %s" - cur.execute(query, params) - conn.commit() - return cur.rowcount > 0 + cur.execute(query, params) + conn.commit() + success = cur.rowcount > 0 + if success: + logger.debug(f"Пользователь {user_id} успешно обновлен") + else: + logger.warning(f"Пользователь {user_id} не найден для обновления") + return success + except Exception as e: + logger.error(f"Ошибка обновления пользователя {user_id}: {e}") + return False def delete_user(self, user_id: int) -> bool: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("DELETE FROM users WHERE id = %s", (user_id,)) - conn.commit() - return cur.rowcount > 0 + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM users WHERE id = %s", (user_id,)) + conn.commit() + success = cur.rowcount > 0 + if success: + logger.debug(f"Пользователь {user_id} удален") + else: + logger.warning(f"Пользователь {user_id} не найден для удаления") + return success + except Exception as e: + logger.error(f"Ошибка удаления пользователя {user_id}: {e}") + return False def get_users_by_role(self, role: str) -> List[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT * FROM users WHERE role = %s ORDER BY name", (role,)) - return [ - User(*row) for row in cur.fetchall() - ] + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT * FROM users WHERE role = %s ORDER BY name", (role,)) + users = [User(*row) for row in cur.fetchall()] + logger.debug(f"Получено {len(users)} пользователей с ролью {role}") + return users + except Exception as e: + logger.error(f"Ошибка получения пользователей по роли {role}: {e}") + return [] def change_password(self, user_id: int, new_password_hash: str) -> bool: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute(""" - UPDATE users - SET password_hash = %s - WHERE id = %s - """, (new_password_hash, user_id)) - conn.commit() - return cur.rowcount > 0 + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + UPDATE users + SET password_hash = %s + WHERE id = %s + """, (new_password_hash, user_id)) + conn.commit() + success = cur.rowcount > 0 + if success: + logger.debug(f"Пароль пользователя {user_id} изменен") + else: + logger.warning(f"Пользователь {user_id} не найден для смены пароля") + return success + except Exception as e: + logger.error(f"Ошибка смены пароля пользователя {user_id}: {e}") + return False def authenticate_user(self, email: str, password_hash: str) -> Optional[User]: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute(""" - SELECT * FROM users - WHERE email = %s AND password_hash = %s - """, (email, password_hash)) - row = cur.fetchone() - if row: - return User(*row) - return None + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute(""" + SELECT * FROM users + WHERE email = %s AND password_hash = %s + """, (email, password_hash)) + row = cur.fetchone() + if row: + logger.debug(f"Успешная аутентификация пользователя {email}") + return User(*row) + logger.warning(f"Неудачная аутентификация пользователя {email}") + return None + except Exception as e: + logger.error(f"Ошибка аутентификации пользователя {email}: {e}") + return None def is_valid_authenticate(self, email: str, password_hash: str) -> bool: - 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)) - return cur.fetchone() is not None + 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: - with get_connection() as conn: - with conn.cursor() as cur: - cur.execute("SELECT 1 FROM users WHERE email = %s", (email,)) - return cur.fetchone() is not None \ No newline at end of file + try: + with get_connection() as conn: + with conn.cursor() as cur: + cur.execute("SELECT 1 FROM users WHERE email = %s", (email,)) + exists = cur.fetchone() is not None + if exists: + logger.debug(f"Пользователь с email {email} существует") + else: + logger.debug(f"Пользователь с email {email} не существует") + return exists + except Exception as e: + logger.error(f"Ошибка проверки существования пользователя {email}: {e}") + return False diff --git a/model/ai_prediction.py b/model/ai_prediction.py index 1685105..acf9978 100644 --- a/model/ai_prediction.py +++ b/model/ai_prediction.py @@ -9,4 +9,4 @@ class AIPrediction: days_until_stockout: int recommended_order: int confidence_score: float - created_at: datetime \ No newline at end of file + created_at: datetime diff --git a/model/inventory.py b/model/inventory.py index dc7820d..9f42e08 100644 --- a/model/inventory.py +++ b/model/inventory.py @@ -11,4 +11,4 @@ class InventoryRecord: shelf_number: int status: str scanned_at: datetime - created_at: datetime \ No newline at end of file + created_at: datetime diff --git a/model/product.py b/model/product.py index 8afdd95..4034b37 100644 --- a/model/product.py +++ b/model/product.py @@ -6,4 +6,4 @@ class Product: name: str category: str min_stock: int - optimal_stock: int \ No newline at end of file + optimal_stock: int diff --git a/model/robot.py b/model/robot.py index fd3847e..2e68e18 100644 --- a/model/robot.py +++ b/model/robot.py @@ -10,4 +10,4 @@ class Robot: last_update: datetime current_zone: Optional[str] = None current_row: Optional[int] = None - current_shelf: Optional[int] = None \ No newline at end of file + current_shelf: Optional[int] = None -- 2.52.0 From 9ecb6a83ba26b2c17cb51c32c114bc2f8edfb5df Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Sun, 26 Oct 2025 19:41:11 +0300 Subject: [PATCH 37/40] style(database): Fixed the issue with long function declarations --- db/repositories/ai_prediction_repository.py | 11 ++++++++--- db/repositories/inventory_repository.py | 13 +++++++++++-- db/repositories/robot_repository.py | 19 +++++++++++++++---- db/repositories/user_repository.py | 8 +++++++- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/db/repositories/ai_prediction_repository.py b/db/repositories/ai_prediction_repository.py index 8c6251d..acc3e7f 100644 --- a/db/repositories/ai_prediction_repository.py +++ b/db/repositories/ai_prediction_repository.py @@ -77,9 +77,14 @@ class AIPredictionsRepository: logger.error(f"Ошибка получения последних прогнозов по товарам: {e}") return [] - def create_prediction(self, product_id: str, prediction_date: date, - days_until_stockout: int, recommended_order: int, - confidence_score: float) -> Optional[int]: + def create_prediction( + self, + product_id: str, + prediction_date: date, + days_until_stockout: int, + recommended_order: int, + confidence_score: float + ) -> Optional[int]: try: with get_connection() as conn: with conn.cursor() as cur: diff --git a/db/repositories/inventory_repository.py b/db/repositories/inventory_repository.py index 5bcbc31..0d5611d 100644 --- a/db/repositories/inventory_repository.py +++ b/db/repositories/inventory_repository.py @@ -48,8 +48,17 @@ class InventoryRepository: logger.error(f"Ошибка получения записи инвентаризации {record_id}: {e}") return None - def create_record(self, robot_id: str, product_id: str, quantity: int, zone: str, - row_number: int, shelf_number: int, status: str, scanned_at: datetime) -> Optional[int]: + def create_record( + self, + robot_id: str, + product_id: str, + quantity: int, + zone: str, + row_number: int, + shelf_number: int, + status: str, + scanned_at: datetime + ) -> Optional[int]: try: with get_connection() as conn: with conn.cursor() as cur: diff --git a/db/repositories/robot_repository.py b/db/repositories/robot_repository.py index 74879b9..7ebd872 100644 --- a/db/repositories/robot_repository.py +++ b/db/repositories/robot_repository.py @@ -1,4 +1,3 @@ -# db/repositories/robot_repository.py from typing import List, Optional import logging from db.connection import get_connection @@ -44,8 +43,15 @@ class RobotRepository: logger.error(f"Ошибка получения робота {robot_id}: {e}") return None - def update_robot(self, robot_id: str, status: str = None, battery_level: int = None, - current_zone: str = None, current_row: int = None, current_shelf: int = None) -> bool: + def update_robot( + self, + robot_id: str, + status: str = None, + battery_level: int = None, + current_zone: str = None, + current_row: int = None, + current_shelf: int = None + ) -> bool: try: with get_connection() as conn: with conn.cursor() as cur: @@ -127,7 +133,12 @@ class RobotRepository: logger.error(f"Ошибка получения роботов в зоне {zone}: {e}") return [] - def create_robot(self, robot_id: str, status: str = 'active', battery_level: int = 100) -> bool: + def create_robot( + self, + robot_id: str, + status: str = 'active', + battery_level: int = 100 + ) -> bool: try: with get_connection() as conn: with conn.cursor() as cur: diff --git a/db/repositories/user_repository.py b/db/repositories/user_repository.py index d3a2a11..4c7413b 100644 --- a/db/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -57,7 +57,13 @@ class UserRepository: logger.error(f"Ошибка получения пользователя по email {email}: {e}") return None - def create_user(self, email: str, password_hash: str, name: str, role: str) -> Optional[User]: + def create_user( + self, + email: str, + password_hash: str, + name: str, + role: str + ) -> Optional[User]: try: with get_connection() as conn: with conn.cursor() as cur: -- 2.52.0 From 72f766ab467453f16bb7710b67e0e23ba3a07c0f Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 23:07:43 +0300 Subject: [PATCH 38/40] fix(log): replace logger with loguru (again!) --- db/connection.py | 19 +++++++++---------- db/repositories/ai_prediction_repository.py | 4 +--- db/repositories/inventory_repository.py | 4 +--- db/repositories/product_repository.py | 4 +--- db/repositories/robot_repository.py | 4 +--- db/repositories/user_repository.py | 4 +--- 6 files changed, 14 insertions(+), 25 deletions(-) diff --git a/db/connection.py b/db/connection.py index 3950913..f5d1825 100644 --- a/db/connection.py +++ b/db/connection.py @@ -1,6 +1,6 @@ import psycopg2 import os -import logging +from loguru import logger from contextlib import contextmanager from typing import Generator @@ -8,8 +8,6 @@ from utils.loadDotEnv import initializeENV initializeENV() -logger = logging.getLogger(__name__) - def PSQLConnect(): conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) return conn @@ -24,16 +22,16 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: conn = None try: conn = PSQLConnect() - logger.debug("Подключение к БД установлено") + logger.info("Подключение к БД установлено") yield conn except psycopg2.OperationalError as e: - logger.error(f"Ошибка подключения к БД: {e}") + logger.error(f"Ошибка подключения к БД:\n{e}") raise except psycopg2.Error as e: - logger.error(f"Ошибка PostgreSQL: {e}") + logger.error(f"Ошибка PostgreSQL:\n{e}") raise except Exception as e: - logger.error(f"Неожиданная ошибка при работе с БД: {e}") + logger.error(f"Неожиданная ошибка при работе с БД:\n{e}") raise finally: if conn: @@ -41,7 +39,7 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: conn.close() logger.debug("Соединение с БД закрыто") except Exception as e: - logger.warning(f"Ошибка при закрытии соединения: {e}") + logger.warning(f"Ошибка при закрытии соединения:\n{e}") def test_connection() -> bool: try: @@ -56,6 +54,7 @@ def test_connection() -> bool: cur.close() logger.debug("Курсор закрыт") except Exception as e: - logger.error(f"Тест подключения к БД провален: {e}") + logger.error(f"Тест подключения к БД провален:\n{e}") return False -print(test_connection()) \ No newline at end of file + +logger.debug(test_connection()) \ No newline at end of file diff --git a/db/repositories/ai_prediction_repository.py b/db/repositories/ai_prediction_repository.py index acc3e7f..176fd9c 100644 --- a/db/repositories/ai_prediction_repository.py +++ b/db/repositories/ai_prediction_repository.py @@ -1,11 +1,9 @@ from typing import List, Optional from datetime import datetime, date -import logging +from loguru import logger from db.connection import get_connection from model.ai_prediction import AIPrediction -logger = logging.getLogger(__name__) - class AIPredictionsRepository: def get_all(self) -> List[AIPrediction]: try: diff --git a/db/repositories/inventory_repository.py b/db/repositories/inventory_repository.py index 0d5611d..c6873a1 100644 --- a/db/repositories/inventory_repository.py +++ b/db/repositories/inventory_repository.py @@ -1,12 +1,10 @@ # db/repositories/inventory_repository.py from typing import List, Optional, Tuple from datetime import datetime -import logging +from loguru import logger from db.connection import get_connection from model.inventory import InventoryRecord -logger = logging.getLogger(__name__) - class InventoryRepository: def get_all(self) -> List[InventoryRecord]: try: diff --git a/db/repositories/product_repository.py b/db/repositories/product_repository.py index 6de73b8..7fc3cbb 100644 --- a/db/repositories/product_repository.py +++ b/db/repositories/product_repository.py @@ -1,10 +1,8 @@ from typing import List, Optional -import logging +from loguru import logger from db.connection import get_connection from model.product import Product -logger = logging.getLogger(__name__) - class ProductRepository: def get_all(self) -> List[Product]: try: diff --git a/db/repositories/robot_repository.py b/db/repositories/robot_repository.py index 7ebd872..3ae1aff 100644 --- a/db/repositories/robot_repository.py +++ b/db/repositories/robot_repository.py @@ -1,10 +1,8 @@ from typing import List, Optional -import logging +from loguru import logger from db.connection import get_connection from model.robot import Robot -logger = logging.getLogger(__name__) - class RobotRepository: def get_all(self) -> List[Robot]: try: diff --git a/db/repositories/user_repository.py b/db/repositories/user_repository.py index 4c7413b..0563d1e 100644 --- a/db/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -1,10 +1,8 @@ from typing import List, Optional -import logging +from loguru import logger from model.user import User from db.connection import get_connection -logger = logging.getLogger(__name__) - class UserRepository: def get_all(self) -> List[User]: try: -- 2.52.0 From e44696ce044d66ac99483c34586ad45cc527902d Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Sun, 26 Oct 2025 22:52:07 +0300 Subject: [PATCH 39/40] wip --- api/auth.py | 11 +++++++++-- db/connection.py | 2 +- db/repositories/user_repository.py | 21 +++------------------ model/user.py | 11 ----------- utils/token.py | 14 ++++++++++++-- 5 files changed, 25 insertions(+), 34 deletions(-) diff --git a/api/auth.py b/api/auth.py index 0c357e8..b62c6e5 100644 --- a/api/auth.py +++ b/api/auth.py @@ -1,5 +1,7 @@ from flask import Blueprint, request, jsonify 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__) @@ -21,8 +23,13 @@ def login(): if len(password.strip()) < 8: return "Password is too short", 400 - user = User(email, password) - return jsonify(user.toJson()) + user = UserRepository().authenticate_user(email, password) + 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: return "Request is not a json", 400 diff --git a/db/connection.py b/db/connection.py index f5d1825..5beb761 100644 --- a/db/connection.py +++ b/db/connection.py @@ -9,7 +9,7 @@ from utils.loadDotEnv import initializeENV initializeENV() def PSQLConnect(): - conn = psycopg2.connect(os.getenv('POSTDRESS_CONNECTION')) + conn = psycopg2.connect(os.getenv('POSTGRES_URL')) return conn def PSQLCursor(conn): diff --git a/db/repositories/user_repository.py b/db/repositories/user_repository.py index 0563d1e..a5c88f2 100644 --- a/db/repositories/user_repository.py +++ b/db/repositories/user_repository.py @@ -166,6 +166,9 @@ class UserRepository: return False def authenticate_user(self, email: str, password_hash: str) -> Optional[User]: + if not self.user_exists(email): + return + try: with get_connection() as conn: with conn.cursor() as cur: @@ -183,24 +186,6 @@ class UserRepository: logger.error(f"Ошибка аутентификации пользователя {email}: {e}") 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: try: with get_connection() as conn: diff --git a/model/user.py b/model/user.py index d98c9c7..e9ea4da 100644 --- a/model/user.py +++ b/model/user.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from datetime import datetime -from utils.token import generateKey @dataclass @@ -11,13 +10,3 @@ class User: name: str role: str 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} diff --git a/utils/token.py b/utils/token.py index d20cea4..d14e602 100644 --- a/utils/token.py +++ b/utils/token.py @@ -1,8 +1,18 @@ import jwt import os from time import time +from model.user import User -def generateKey(email, passwd): +def generateKey(user: User) -> dict: 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 -- 2.52.0 From 052c93928c2fd2f2f415f1e5791400ace925c035 Mon Sep 17 00:00:00 2001 From: Kita Trofimov <144846094+NiTro005@users.noreply.github.com> Date: Mon, 27 Oct 2025 23:02:19 +0300 Subject: [PATCH 40/40] feat(db): Database initialization has been added --- api/loginapi.py | 3 +- db/connection.py | 58 ++++++++++--------- db/init.py | 147 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 28 deletions(-) create mode 100644 db/init.py diff --git a/api/loginapi.py b/api/loginapi.py index 3ca2e88..299d109 100644 --- a/api/loginapi.py +++ b/api/loginapi.py @@ -1,5 +1,6 @@ from flask import Blueprint, request -from model.user import user + +from model import user loginBP = Blueprint("loginapi", __name__) diff --git a/db/connection.py b/db/connection.py index 3950913..20e1425 100644 --- a/db/connection.py +++ b/db/connection.py @@ -1,21 +1,29 @@ import psycopg2 import os -import logging from contextlib import contextmanager from typing import Generator +from loguru import logger from utils.loadDotEnv import initializeENV initializeENV() -logger = logging.getLogger(__name__) 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 + def PSQLCursor(conn): - cur = conn.cursor() + cur = conn.cursor() + logger.debug("Курсор БД создан") return cur @@ -24,38 +32,34 @@ def get_connection() -> Generator[psycopg2.extensions.connection, None, None]: conn = None try: conn = PSQLConnect() - logger.debug("Подключение к БД установлено") + logger.debug("Контекст подключения к БД открыт") 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: - logger.error(f"Неожиданная ошибка при работе с БД: {e}") + logger.error(f"Ошибка в контексте подключения: {e}") + if conn: + conn.rollback() + logger.debug("Откат транзакции выполнен") raise finally: if conn: - try: - conn.close() - logger.debug("Соединение с БД закрыто") - except Exception as e: - logger.warning(f"Ошибка при закрытии соединения: {e}") + conn.close() + logger.debug("Подключение к БД закрыто") + def test_connection() -> bool: try: with get_connection() as conn: cur = PSQLCursor(conn) - try: - cur.execute("SELECT version();") - version = cur.fetchone() - logger.debug(f"Версия PostgreSQL: {version[0]}") - return True - finally: - cur.close() - logger.debug("Курсор закрыт") + cur.execute("SELECT version();") + version = cur.fetchone() + logger.info(f"Подключение к БД успешно: {version[0]}") + cur.close() + logger.debug("Курсор БД закрыт") + return True except Exception as e: - logger.error(f"Тест подключения к БД провален: {e}") + logger.error(f"Ошибка подключения к БД: {e}") return False -print(test_connection()) \ No newline at end of file + + +if __name__ == "__main__": + test_connection() \ No newline at end of file diff --git a/db/init.py b/db/init.py new file mode 100644 index 0000000..a7f6556 --- /dev/null +++ b/db/init.py @@ -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() \ No newline at end of file -- 2.52.0