diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..a60b3be --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +#ignore = E226,E302,E41 +max-line-length = 130 +max-complexity = 10 diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index ec345f4..31c33e5 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -1,14 +1,11 @@ name: linters - on: - workflow_run: # ← event name must be workflow_run - workflows: ["Review"] # ← exact name of your triggering workflow - types: [completed] - branches: [Development] - + push: + branches: + - Development jobs: - on-success: + check: if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: @@ -30,9 +27,3 @@ jobs: - name: Run pylint run: pylint frontend services utils - - on-failure: - if: ${{ github.event.workflow_run.conclusion == 'failure' }} - runs-on: ubuntu-latest - steps: - - run: echo 'The Review workflow failed' diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 423b63b..9d7ccd1 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -1,13 +1,12 @@ name: Unit Test on: - workflow_run: - workflows: ["linters"] - types: [completed] #requested - + push: + branches: + - main jobs: - on-success: + Unit-Test: runs-on: ubuntu-latest steps: @@ -29,10 +28,4 @@ jobs: # 6. Run unit tests - name: Run pytest - run: pytest - - on-failure: - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'failure' }} - steps: - - run: echo 'The triggering workflow failed' \ No newline at end of file + run: pytest \ No newline at end of file diff --git a/config/__pycache__/database.cpython-312.pyc b/config/__pycache__/database.cpython-312.pyc new file mode 100644 index 0000000..71bd015 Binary files /dev/null and b/config/__pycache__/database.cpython-312.pyc differ diff --git a/config/database.py b/config/database.py index 2f39957..7b6dc6e 100644 --- a/config/database.py +++ b/config/database.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from typing import Any, Dict @@ -10,7 +11,15 @@ class DatabaseConfig: def _load_config(self) -> Dict[str, Any]: if self.db_type.lower() == "sqlite": - return {"path": os.getenv("SQLITE_PATH", "databases/chatbot.db")} + # Standardpfad relativ zum Projektroot + default_path = Path(__file__).parent.parent / "databases" / "chatbot.db" + sqlite_path = os.getenv("SQLITE_PATH", str(default_path)) + + # Stelle sicher, dass wir einen absoluten Pfad haben + if not Path(sqlite_path).is_absolute(): + sqlite_path = Path.cwd() / sqlite_path + + return {"path": str(sqlite_path)} elif self.db_type.lower() == "mysql": return { "host": os.getenv("MYSQL_HOST", "localhost"), diff --git a/database/__pycache__/__init__.cpython-312.pyc b/database/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..5989d4a Binary files /dev/null and b/database/__pycache__/__init__.cpython-312.pyc differ diff --git a/database/__pycache__/flask_integration.cpython-312.pyc b/database/__pycache__/flask_integration.cpython-312.pyc new file mode 100644 index 0000000..9d821a4 Binary files /dev/null and b/database/__pycache__/flask_integration.cpython-312.pyc differ diff --git a/database/__pycache__/interface.cpython-312.pyc b/database/__pycache__/interface.cpython-312.pyc new file mode 100644 index 0000000..c3b58ab Binary files /dev/null and b/database/__pycache__/interface.cpython-312.pyc differ diff --git a/database/__pycache__/sqlite_db.cpython-312.pyc b/database/__pycache__/sqlite_db.cpython-312.pyc new file mode 100644 index 0000000..0133928 Binary files /dev/null and b/database/__pycache__/sqlite_db.cpython-312.pyc differ diff --git a/database/flask_integration.py b/database/flask_integration.py index 1300051..883becd 100644 --- a/database/flask_integration.py +++ b/database/flask_integration.py @@ -1,6 +1,6 @@ import logging -from flask import current_app, g +from flask import g from .interface import DatabaseInterface diff --git a/database/interface.py b/database/interface.py index 0c05695..841bf1c 100644 --- a/database/interface.py +++ b/database/interface.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Any, Dict, List, Optional +from typing import Any, Dict, Optional class DatabaseInterface(ABC): diff --git a/database/sqlite_db.py b/database/sqlite_db.py index 1e5074a..0d9ef02 100644 --- a/database/sqlite_db.py +++ b/database/sqlite_db.py @@ -1,5 +1,6 @@ import logging import sqlite3 +from pathlib import Path from threading import local from typing import Any, Dict, Optional @@ -13,32 +14,50 @@ class SQLiteDatabase(DatabaseInterface): def __init__(self, db_path: str): self.db_path = db_path + # Erstelle Verzeichnis falls nicht vorhanden + self._ensure_database_directory() # Thread-local storage für Verbindungen self._local = local() + def _ensure_database_directory(self) -> None: + """Stellt sicher, dass das Database-Verzeichnis existiert""" + db_dir = Path(self.db_path).parent + if not db_dir.exists(): + try: + db_dir.mkdir(parents=True, exist_ok=True) + logger.info(f"Created database directory: {db_dir}") + except OSError as e: + logger.error(f"Failed to create database directory {db_dir}: {e}") + raise + def _get_connection(self) -> sqlite3.Connection: """Holt oder erstellt eine thread-lokale Verbindung""" if not hasattr(self._local, "connection") or self._local.connection is None: - self._local.connection = sqlite3.connect( - self.db_path, - check_same_thread=False, # Erlaubt thread-übergreifende Nutzung - timeout=30.0, - detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES, - ) - self._local.connection.row_factory = sqlite3.Row - # Optimierungen für bessere Performance - self._local.connection.execute("PRAGMA journal_mode=WAL") - self._local.connection.execute("PRAGMA synchronous=NORMAL") - self._local.connection.execute("PRAGMA cache_size=1000") - self._local.connection.execute("PRAGMA temp_store=MEMORY") - logger.debug(f"New SQLite connection created for thread") - + try: + # Absoluten Pfad verwenden für bessere Kompatibilität + abs_path = Path(self.db_path).resolve() + self._local.connection = sqlite3.connect( + str(abs_path), + check_same_thread=False, # Erlaubt thread-übergreifende Nutzung + timeout=30.0, + detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES, + ) + self._local.connection.row_factory = sqlite3.Row + # Optimierungen für bessere Performance + self._local.connection.execute("PRAGMA journal_mode=WAL") + self._local.connection.execute("PRAGMA synchronous=NORMAL") + self._local.connection.execute("PRAGMA cache_size=1000") + self._local.connection.execute("PRAGMA temp_store=MEMORY") + logger.debug(f"New SQLite connection created for thread: {abs_path}") + except sqlite3.Error as e: + logger.error(f"Failed to connect to SQLite database at {abs_path}: {e}") + raise return self._local.connection def connect(self) -> None: """Initialisiert die thread-lokale Verbindung""" try: - conn = self._get_connection() + self._get_connection() # Entfernt das unbenutzte 'conn' logger.info(f"Connected to SQLite database: {self.db_path}") except Exception as e: logger.error(f"Failed to connect to SQLite database: {e}") @@ -61,14 +80,14 @@ class SQLiteDatabase(DatabaseInterface): try: cursor.execute( """ - CREATE TABLE IF NOT EXISTS users - ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT NOT NULL UNIQUE, - email TEXT NOT NULL UNIQUE, - password_hash TEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) - """ + CREATE TABLE IF NOT EXISTS users + ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) + """ ) conn.commit() logger.info("User table created/verified") diff --git a/frontend/__pycache__/app.cpython-312.pyc b/frontend/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000..c2a6ba3 Binary files /dev/null and b/frontend/__pycache__/app.cpython-312.pyc differ diff --git a/main.py b/main.py index 49e1f9f..0c9fee6 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,12 @@ import logging import os +from dotenv import load_dotenv + from frontend.app import app +load_dotenv() + # Logging konfigurieren logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" @@ -15,6 +19,7 @@ if __name__ == "__main__": debug = os.getenv("FLASK_DEBUG", "False").lower() == "true" host = os.getenv("FLASK_HOST", "0.0.0.0") port = int(os.getenv("FLASK_PORT", "8080")) + print(port) logger.info(f"Starting ChatBot application on {host}:{port}") logger.info(f"Debug mode: {debug}") diff --git a/requirements.txt b/requirements.txt index 2bc2985..1b37a24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,36 +1,28 @@ -# Requirements for PY_ChatBot -# Core Framework -Flask>=2.3.0,<3.0.0 -Werkzeug>=2.3.0,<3.0.0 - -# Database & ORM (Optional for future expansion) -SQLAlchemy>=2.0.0,<3.0.0 - -# Environment & Configuration -python-dotenv>=1.0.0 - -# Security & Authentication -bcrypt>=4.0.0 - -# HTTP Client (for health checks and external APIs) -requests>=2.31.0 - -# Date/Time utilities -python-dateutil>=2.8.0 - -# Development Dependencies (commented out for production) -# pytest>=7.4.0 -# pytest-flask>=1.2.0 -# pytest-cov>=4.1.0 -# black>=23.7.0 -# flake8>=6.0.0 -# mypy>=1.5.0 - -# Production WSGI Server -gunicorn>=21.2.0 - -# Logging -colorlog>=6.7.0 - -# Utility -click>=8.1.0 \ No newline at end of file +astroid==3.3.11 +black==25.9.0 +blinker==1.9.0 +click==8.3.0 +dill==0.4.0 +dotenv==0.9.9 +flake8==7.3.0 +Flask==3.1.2 +iniconfig==2.1.0 +isort==6.1.0 +itsdangerous==2.2.0 +Jinja2==3.1.6 +MarkupSafe==3.0.3 +mccabe==0.7.0 +mypy_extensions==1.1.0 +packaging==25.0 +pathspec==0.12.1 +platformdirs==4.4.0 +pluggy==1.6.0 +pycodestyle==2.14.0 +pyflakes==3.4.0 +Pygments==2.19.2 +pylint==3.3.8 +pytest==8.4.2 +python-dotenv==1.1.1 +pytokens==0.1.10 +tomlkit==0.13.3 +Werkzeug==3.1.3 diff --git a/services/__pycache__/__init__.cpython-312.pyc b/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..d7639ef Binary files /dev/null and b/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/services/__pycache__/auth_service.cpython-312.pyc b/services/__pycache__/auth_service.cpython-312.pyc new file mode 100644 index 0000000..cc9e2e4 Binary files /dev/null and b/services/__pycache__/auth_service.cpython-312.pyc differ diff --git a/services/__pycache__/user_service.cpython-312.pyc b/services/__pycache__/user_service.cpython-312.pyc new file mode 100644 index 0000000..68d0cf0 Binary files /dev/null and b/services/__pycache__/user_service.cpython-312.pyc differ diff --git a/services/user_service.py b/services/user_service.py index 83fa5b7..2cf6634 100644 --- a/services/user_service.py +++ b/services/user_service.py @@ -15,36 +15,41 @@ class UserService: def create_user( self, username: str, email: str, password: str ) -> tuple[bool, list[str]]: - errors = [] + errors = self._validate_user_inputs(username, email, password) + if errors: + return False, errors - # Validierung + errors = self._check_user_existence(username, email) + if errors: + return False, errors + + password_hash = PasswordUtils.hash_password_simple(password) + return self._attempt_user_creation(username, email, password_hash) + + def _validate_user_inputs( + self, username: str, email: str, password: str + ) -> list[str]: + errors = [] if not ValidationUtils.validate_username(username): errors.append("Invalid username format") - if not ValidationUtils.validate_email(email): errors.append("Invalid email format") - password_valid, password_errors = ValidationUtils.validate_password(password) if not password_valid: errors.extend(password_errors) + return errors - if errors: - return False, errors - - # Prüfe auf existierende User + def _check_user_existence(self, username: str, email: str) -> list[str]: + errors = [] if self.get_user_by_email(email): errors.append("Email already registered") - if self.get_user_by_username(username): errors.append("Username already taken") + return errors - if errors: - return False, errors - - # Passwort hashen - password_hash = PasswordUtils.hash_password_simple(password) - - # User erstellen + def _attempt_user_creation( + self, username: str, email: str, password_hash: str + ) -> tuple[bool, list[str]]: try: success = self.db.create_user(username, email.lower(), password_hash) if success: diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..e69de29 diff --git a/utils/__pycache__/__init__.cpython-312.pyc b/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..f45d26b Binary files /dev/null and b/utils/__pycache__/__init__.cpython-312.pyc differ diff --git a/utils/__pycache__/auth_decorators.cpython-312.pyc b/utils/__pycache__/auth_decorators.cpython-312.pyc new file mode 100644 index 0000000..c64c311 Binary files /dev/null and b/utils/__pycache__/auth_decorators.cpython-312.pyc differ diff --git a/utils/__pycache__/password_utils.cpython-312.pyc b/utils/__pycache__/password_utils.cpython-312.pyc new file mode 100644 index 0000000..fb59076 Binary files /dev/null and b/utils/__pycache__/password_utils.cpython-312.pyc differ diff --git a/utils/__pycache__/validation.cpython-312.pyc b/utils/__pycache__/validation.cpython-312.pyc new file mode 100644 index 0000000..7a3e984 Binary files /dev/null and b/utils/__pycache__/validation.cpython-312.pyc differ