From ea1615995d9d0592d39a57ac32546014119fc32e Mon Sep 17 00:00:00 2001 From: florian Date: Sun, 5 Oct 2025 20:53:07 +0200 Subject: [PATCH] Deployment configuration --- Dockerfile | 13 ++++ app_database.sql | 10 +-- db.py | 42 ------------- hvac_handler.py | 13 ---- src/db.py | 62 +++++++++++++++++++ src/hvac_handler.py | 43 +++++++++++++ logger_handler.py => src/logger_handler.py | 0 main.py => src/main.py | 0 .../uvicorn_logging_config.py | 0 validator.py => src/validator.py | 0 10 files changed, 123 insertions(+), 60 deletions(-) create mode 100644 Dockerfile delete mode 100644 db.py delete mode 100644 hvac_handler.py create mode 100644 src/db.py create mode 100644 src/hvac_handler.py rename logger_handler.py => src/logger_handler.py (100%) rename main.py => src/main.py (100%) rename uvicorn_logging_config.py => src/uvicorn_logging_config.py (100%) rename validator.py => src/validator.py (100%) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..aac7798 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.12-slim + +COPY requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +WORKDIR /app + +COPY src/ /app/ + +ENTRYPOINT ["sh", "-c", "sleep 10 && python main.py"] + + diff --git a/app_database.sql b/app_database.sql index a8cdfda..dca00af 100644 --- a/app_database.sql +++ b/app_database.sql @@ -2,18 +2,18 @@ CREATE TABLE users ( user_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, username VARCHAR(100) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, - password_hash CHAR(64) DEFAULT NULL, -- SHA256 hash or similar - api_key VARCHAR(255) UNIQUE NOT NULL, -- for API authentication + password_hash CHAR(64) DEFAULT NULL, + api_key VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_login TIMESTAMP NULL DEFAULT NULL, status ENUM('active','inactive','banned') NOT NULL DEFAULT 'active' ); CREATE TABLE device_tokens ( - token_id CHAR(36) NOT NULL PRIMARY KEY, -- UUID - user_id BIGINT NOT NULL, -- Foreign key + token_id CHAR(36) NOT NULL PRIMARY KEY, + user_id BIGINT NOT NULL, platform ENUM('ios','android','web') NOT NULL, - token VARBINARY(512) NOT NULL, -- encrypted token + token VARBINARY(512) NOT NULL, status ENUM('active','invalid','expired') NOT NULL DEFAULT 'active', app_version VARCHAR(50), locale VARCHAR(10), diff --git a/db.py b/db.py deleted file mode 100644 index 672f529..0000000 --- a/db.py +++ /dev/null @@ -1,42 +0,0 @@ -import mysql.connector -from mysql.connector import pooling -import threading - -MYSQL_CONFIG = { - "host": "localhost", - "user": "florian", - "password": "password123++", - "database": "app" -} - -# Lock to ensure thread-safe pool creation -_pool_lock = threading.Lock() -_connection_pool = None - -def get_connection_pool(): - global _connection_pool - with _pool_lock: - if _connection_pool is None: - _connection_pool = mysql.connector.pooling.MySQLConnectionPool( - pool_name="mypool", - pool_size=5, - pool_reset_session=True, - **MYSQL_CONFIG - ) - return _connection_pool - -# Dependency for FastAPI -def get_db(): - pool = get_connection_pool() - conn = pool.get_connection() - try: - yield conn - finally: - conn.close() - -if __name__ == "__main__": - # Manual test - for conn in get_db(): - cursor = conn.cursor(dictionary=True) - cursor.execute("SELECT NOW() AS ts") - print(cursor.fetchone()) diff --git a/hvac_handler.py b/hvac_handler.py deleted file mode 100644 index b99eff4..0000000 --- a/hvac_handler.py +++ /dev/null @@ -1,13 +0,0 @@ -import hvac -import base64 -import os - -HVAC_AGENT_URL = os.getenv("HVAC_AGENT_URL","http://vault-agent:8201") -client = hvac.Client(url=HVAC_AGENT_URL) - -def encrypt_token(token: str) -> str: - response = client.secrets.transit.encrypt_data( - name='push-tokens', - plaintext=base64.b64encode(token.encode()).decode() - ) - return response['data']['ciphertext'] \ No newline at end of file diff --git a/src/db.py b/src/db.py new file mode 100644 index 0000000..a2516df --- /dev/null +++ b/src/db.py @@ -0,0 +1,62 @@ +import mysql.connector +from mysql.connector import pooling +import threading +from hvac_handler import get_secret +import os +import time +import sys + + +db_username = get_secret("secret/api/db", "username") +db_password = get_secret("secret/api/db", "password") +db_host = os.getenv("BACKEND_API_DB_HOST","localhost") +db_database = os.getenv("BACKEND_API_DB_DATABASE","app") + +MAX_RETRIES = 5 +RETRY_DELAY = 5 + +MYSQL_CONFIG = { + "host": db_host, + "user": db_username, + "password": db_password, + "database": db_database +} + +_pool_lock = threading.Lock() +_connection_pool = None + +def create_connection_pool(): + global _connection_pool + for attempt in range(1, MAX_RETRIES+1): + try: + print(f"[MySQL] Attempt {attempt} to connect...") + _connection_pool = mysql.connector.pooling.MySQLConnectionPool( + pool_name="mypool", + pool_size=5, + pool_reset_session=True, + **MYSQL_CONFIG + ) + print("[MySQL] Connection pool created successfully.") + return + except mysql.connector.Error as e: + print(f"[MySQL] Attempt {attempt} failed: {e}") + if attempt < MAX_RETRIES: + time.sleep(RETRY_DELAY) + print(f"[MySQL] Failed to connect after {MAX_RETRIES} attempts — exiting.") + sys.exit(1) + +def get_connection_pool(): + global _connection_pool + with _pool_lock: + if _connection_pool is None: + create_connection_pool + return _connection_pool + +def get_db(): + pool = get_connection_pool() + conn = pool.get_connection() + try: + yield conn + finally: + conn.close() + diff --git a/src/hvac_handler.py b/src/hvac_handler.py new file mode 100644 index 0000000..b1dad1a --- /dev/null +++ b/src/hvac_handler.py @@ -0,0 +1,43 @@ +import hvac +import base64 +import os +import time +import sys + +HVAC_AGENT_URL = os.getenv("HVAC_AGENT_URL","http://vault-agent:8201") + +MAX_RETRIES = 5 +BACKOFF = 5 + +def get_client(): + for attempt in range(1, MAX_RETRIES+1): + try: + client = hvac.Client(url=HVAC_AGENT_URL) + if client.is_authenticated(): + return client + raise Exception("Not authenticated") + except Exception as e: + print(f"Vault connection failed (attempt {attempt}/{MAX_RETRIES}): {e}") + time.sleep(BACKOFF * attempt) + print("Vault unreachable after retries. Exiting.") + sys.exit(1) + +client = get_client() + +def get_secret(path:str, key:str): + try: + secret = client.secrets.kv.v2.read_secret_version( + mount_point="kv", + path=path + ) + return secret["data"]["data"][key] + except Exception as e: + print(f"Failed to fetch secret '{path}:{key}': {e}") + sys.exit(1) + +def encrypt_token(token: str) -> str: + response = client.secrets.transit.encrypt_data( + name='push-tokens', + plaintext=base64.b64encode(token.encode()).decode() + ) + return response['data']['ciphertext'] \ No newline at end of file diff --git a/logger_handler.py b/src/logger_handler.py similarity index 100% rename from logger_handler.py rename to src/logger_handler.py diff --git a/main.py b/src/main.py similarity index 100% rename from main.py rename to src/main.py diff --git a/uvicorn_logging_config.py b/src/uvicorn_logging_config.py similarity index 100% rename from uvicorn_logging_config.py rename to src/uvicorn_logging_config.py diff --git a/validator.py b/src/validator.py similarity index 100% rename from validator.py rename to src/validator.py