diff --git a/Dockerfile b/Dockerfile index d58a36f..d4930bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,11 @@ ENV PYTHONUNBUFFERED 1 WORKDIR /app +RUN apt-get update &&\ + apt-get install -y \ + default-mysql-client &&\ + apt-get clean + COPY requirements.txt /app/ RUN pip install --no-cache-dir -r requirements.txt diff --git a/api/path.py b/api/path.py index 8ba774c..acf82c3 100644 --- a/api/path.py +++ b/api/path.py @@ -40,7 +40,7 @@ def create_path(): if not data or 'name' not in data or 'parent_id' not in data: return jsonify({"error": "bad request"}), 400 with get_db() as session: - if data['parent_id'] != 0 and not session.query(Path).get(data['parent_id']): + if data['parent_id'] != 1 and not session.query(Path).get(data['parent_id']): return jsonify({"error": "path not found"}), 404 if session.query(Path).filter_by(name=data['name'], parent_id=data['parent_id']).first(): return jsonify({"error": "Path already exists under the parent"}), 409 diff --git a/app.py b/app.py index 352af7d..ac13b72 100644 --- a/app.py +++ b/app.py @@ -1,13 +1,8 @@ # app.py from pprint import pprint - -from sqlalchemy import text - -from db.models.Path import Path from logging_handlers.DatabaseLogHandler import DatabaseLogHandler -from urllib.parse import urlparse from api import limiter -from flask import Flask, request, url_for +from flask import Flask, request from flask_cors import CORS import api import env_provider @@ -24,28 +19,12 @@ logger.addHandler(db_handler) logger.setLevel(logging.INFO) try: - db.create_all() + db.setup_db() except Exception as e: - print(f"db not ready {e}") + print(f"db not ready") + print(e) - -try: - with db.get_db() as session: - root_path = session.query(Path).filter(Path.id == 1).first() - if not root_path: - session.execute(text("SET FOREIGN_KEY_CHECKS=0;")) - #session.execute(text("ALTER TABLE path AUTO_INCREMENT = 0;")) - session.execute(text("ALTER TABLE path MODIFY COLUMN id INT;")) - root_path = Path(id=1, name="") - session.add(root_path) - session.commit() - session.execute(text("ALTER TABLE path MODIFY COLUMN id INT AUTO_INCREMENT;")) - session.execute(text("SET FOREIGN_KEY_CHECKS=1;")) - logger.info("Root path created") -except Exception as e: - logger.error(f"Failed to create root path {e}") app = Flask(__name__) -#app.config['SERVER_NAME'] = env_provider.BACKEND_HOST app.secret_key = env_provider.SESSION_SECRET_KEY CORS(app, resources={r"/api/*": {"origins": [ env_provider.KC_HOST, diff --git a/db/__init__.py b/db/__init__.py index b888265..3c60ec8 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -1,11 +1,16 @@ #db/__init__.py +import os +import pkgutil +import subprocess from contextlib import contextmanager + + from db.models import Base from sqlalchemy.orm import sessionmaker -from sqlalchemy import create_engine -import env_provider -from env_provider import DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD +from sqlalchemy.dialects.mysql import insert +from sqlalchemy import create_engine, text, inspect +from env_provider import DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD, DB_SCHEMA_UPDATED, ENVIRONMENT engine = create_engine(f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}") SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) @@ -18,7 +23,59 @@ def get_db(): finally: db.close() +def dump_db(): + try: + os.environ['MYSQL_PWD'] = DB_PASSWORD + dump_cmd = f"mysqldump --no-tablespaces -h {DB_HOST} -P {DB_PORT} -u {DB_USER} {DB_NAME} > /app/dump/db_dump.sql" + subprocess.run(dump_cmd, shell=True, check=True) + except subprocess.CalledProcessError as e: + print(f"Failed to dump database: {e}") + raise e + +def clear_db(): + with engine.connect() as conn: + conn.execute(text("SET FOREIGN_KEY_CHECKS = 0;")) + inspector = inspect(engine) + for table_name in inspector.get_table_names(): + conn.execute(text(f"DROP TABLE IF EXISTS {table_name}")) + conn.execute(text("SET FOREIGN_KEY_CHECKS = 1;")) + def create_all(): with engine.begin() as conn: - Base.metadata.create_all(bind=conn) \ No newline at end of file + Base.metadata.create_all(bind=conn) + + +def init_payload(): + + from db.models import table_models + + with get_db() as session: + session.execute(text("SET FOREIGN_KEY_CHECKS = 0;")) + for model in table_models: + print(f"MODEL -- {model}, {hasattr(model, '__pay_load__')}") + if hasattr(model, "__pay_load__"): + + payload =model.__pay_load__[ENVIRONMENT] + print(f"- - [ - ] hasattr, {ENVIRONMENT} - {payload}") + stmt = insert(model.__table__).values(payload).prefix_with("IGNORE") + print(f"- - [ - ] {stmt}\n") + session.execute(stmt) + session.execute(text("SET FOREIGN_KEY_CHECKS = 1;")) + session.commit() + + +def setup_db(): + if DB_SCHEMA_UPDATED: + try: + dump_db() + print("[ x ] db dumped") + except Exception as e: + print(f"[ x ] Failed to dump database: {e}") + clear_db() + print("[ x ] db cleared") + create_all() + print("[ x ] db created") + init_payload() + print("[ x ] payload loaded") + diff --git a/db/models/Path.py b/db/models/Path.py index e9aa10a..5c34dd5 100644 --- a/db/models/Path.py +++ b/db/models/Path.py @@ -14,4 +14,15 @@ class Path(Base): "id": self.id, "name": self.name, "parent_id": self.parent_id, - } \ No newline at end of file + } + + __pay_load__ = { + 'dev': [ + {'id': 1, 'name': '', 'parent_id': None}, + {'id': 2, 'name': 'test', 'parent_id': 1}, + {'id': 3, 'name': 'test1', 'parent_id': 1}, + ], + 'prod': [ + {'id': 1, 'name': '', 'parent_id': None}, + ] + } \ No newline at end of file diff --git a/db/models/__init__.py b/db/models/__init__.py index 5bf6f52..f5ed4e5 100644 --- a/db/models/__init__.py +++ b/db/models/__init__.py @@ -5,6 +5,11 @@ import importlib from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() package_name = "db.models" -for _, module_name, _ in pkgutil.iter_modules(__path__): - importlib.import_module(f"{package_name}.{module_name}") +table_models = [] +for _, module_name, _ in pkgutil.iter_modules(__path__): + module = importlib.import_module(f"{package_name}.{module_name}") + if hasattr(module, module_name): + model = getattr(module, module_name) + if hasattr(model, "__tablename__"): + table_models.append(model) diff --git a/env_provider.py b/env_provider.py index ac72eb3..4edddb1 100644 --- a/env_provider.py +++ b/env_provider.py @@ -4,12 +4,17 @@ from dotenv import load_dotenv load_dotenv() +def str_to_bool(value): + return value.lower() in ("yes", "true", "t", "1") + +ENVIRONMENT = os.getenv("ENVIRONMENT", "dev") DB_HOST = os.getenv("DB_HOST") DB_PORT = os.getenv("DB_PORT") DB_NAME = os.getenv("DB_NAME") DB_USER = os.getenv("DB_USER") DB_PASSWORD = os.getenv("DB_PASSWORD") +DB_SCHEMA_UPDATED = str_to_bool(os.getenv("DB_SCHEMA_UPDATED", 'false')) SESSION_SECRET_KEY = os.getenv("SESSION_SECRET_KEY") KC_HOST = os.getenv("KC_HOST") @@ -21,15 +26,17 @@ BACKEND_HOST = os.getenv("BACKEND_HOST") def summerize(): return { + "ENVIRONMENT": ENVIRONMENT, 'DB_HOST': DB_HOST, 'DB_PORT': DB_PORT, 'DB_NAME': DB_NAME, 'DB_USER': DB_USER, 'DB_PASSWORD': DB_PASSWORD, + 'DB_SCHEMA_UPDATED': DB_SCHEMA_UPDATED, 'SESSION_SECRET_KEY': SESSION_SECRET_KEY, 'KC_HOST': KC_HOST, 'KC_REALM': KC_REALM, 'KC_CLIENT_ID': KC_CLIENT_ID, 'FRONTEND_HOST': FRONTEND_HOST, 'BACKEND_HOST': BACKEND_HOST, - } \ No newline at end of file + } diff --git a/play.py b/play.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt index dbb38f1..d53830c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +alembic==1.14.0 Authlib==1.3.2 blinker==1.9.0 certifi==2024.8.30 @@ -15,6 +16,7 @@ idna==3.10 itsdangerous==2.2.0 Jinja2==3.1.4 limits==3.14.1 +Mako==1.3.7 markdown-it-py==3.0.0 MarkupSafe==3.0.2 mdurl==0.1.2