From 5f344aca861a55b69365a94173dbd06929477620 Mon Sep 17 00:00:00 2001
From: Ethan Clay <ethanclay2017@gmail.com>
Date: Fri, 14 Feb 2025 12:40:57 +0000
Subject: [PATCH] Add SQL on inital load, depending on init.sql file

---
 app/__init__.py        | 61 +++++++++++++++++++++++++++++++++++++-----
 app/admin/routes.py    | 19 +++++++++++++
 app/models/listings.py |  3 +++
 docker-compose.yml     |  1 -
 4 files changed, 76 insertions(+), 8 deletions(-)

diff --git a/app/__init__.py b/app/__init__.py
index fa82092..1cbd84a 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -9,6 +9,8 @@ from dotenv import load_dotenv
 from app.logger import auth_logger
 from functools import wraps
 import os
+import pymysql
+from sqlalchemy.sql import text
 
 # Initialize extensions
 db = SQLAlchemy()
@@ -28,11 +30,55 @@ def permission_required(permission):
         return decorated_function
     return decorator
 
-
 super_admin_permission = Permission(RoleNeed('super-admin'))
 admin_permission = Permission(RoleNeed('admin'))
 user_permission = Permission(RoleNeed('user'))
 
+
+def create_database_if_not_exists(db_host, db_user, db_password, db_name):
+    # Connect using Root to create schema if doesn't exist and then create user for that schema
+    connection = pymysql.connect(
+        host=db_host,
+        user='root',
+        password=db_password
+    )
+
+    try:
+        with connection.cursor() as cursor:
+            cursor.execute(f"CREATE DATABASE IF NOT EXISTS {db_name}")
+            # Create the user from .env details
+            cursor.execute(f"CREATE USER IF NOT EXISTS '{db_user}'@'%' IDENTIFIED BY '{db_password}'")
+            cursor.execute(f"GRANT ALL PRIVILEGES ON {db_name}.* TO '{db_user}'@'%'")
+            cursor.execute(f"FLUSH PRIVILEGES")
+        connection.commit()
+    finally:
+        connection.close()
+
+    # Reconnect using user in .env to prevent permission issues
+    connection = pymysql.connect(
+        host=db_host,
+        user=db_user,
+        password=db_password,
+        database=db_name
+    )
+
+    try:
+        with connection.cursor() as cursor:
+            # Check if tables exist
+            cursor.execute("SHOW TABLES;")
+            tables = cursor.fetchall()
+            if not tables:
+                with open('sql-setup/init.sql', 'r') as file:
+                    sql_commands = file.read().split(';')
+                    for command in sql_commands:
+                        if command.strip():
+                            cursor.execute(command)
+                connection.commit()
+    finally:
+        connection.close()
+
+
+
 def create_app(config_class=Config):
     app = Flask(__name__)
     app.config.from_object(config_class)
@@ -48,9 +94,12 @@ def create_app(config_class=Config):
     db_password = os.getenv("DATABASE_PASSWORD")
     db_name = os.getenv("DATABASE_NAME")
     
+    create_database_if_not_exists(db_host, db_user, db_password, db_name)
+    
     app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
     app.config['SQLALCHEMY_DATABASE_URI'] = f"mysql+pymysql://{db_user}:{db_password}@{db_host}/{db_name}"
 
+
     # Initialize extensions with the app
     db.init_app(app)
     migrate.init_app(app, db)
@@ -75,7 +124,6 @@ def create_app(config_class=Config):
             else:
                 auth_logger.debug(f'No role found for user {identity.user.username}.')
 
-
     # Add global template variables
     @app.context_processor
     def set_global_html_variable_values():
@@ -99,13 +147,13 @@ def create_app(config_class=Config):
                 'user_permission': g.user_permission,
                 'super_admin_permission': g.super_admin_permission
             }
-        
+
     # @app.errorhandler(Exception)
     # def handle_exception(e):
     #     app.logger.error(f"Unhandled exception: {e}")
     #     session['error_message'] = str(e)
     #     return redirect(url_for('errors.quandary'))
-    
+
     @app.errorhandler(403)
     def handle_exception(e):
         app.logger.error(f"Unhandled exception: {e}")
@@ -133,10 +181,9 @@ def create_app(config_class=Config):
                     g.admin_permission = admin_permission
                     g.is_admin = True
 
-    login_manager.login_view = 'profile.login'
-    
-    return app
+    login_manager.login_view = 'profile.login' 
 
+    return app
 
 @login_manager.user_loader
 def load_user(user_id):
diff --git a/app/admin/routes.py b/app/admin/routes.py
index 8e4775e..9ca30fe 100644
--- a/app/admin/routes.py
+++ b/app/admin/routes.py
@@ -4,6 +4,7 @@ from app import admin_permission, permission_required, super_admin_permission
 from app.models import Listings, ListingImages, User
 from app.admin import bp
 from app.main.utils import generate_time_options
+from sqlalchemy.sql import text
 
 
 @bp.route('/home')
@@ -296,3 +297,21 @@ def delete_image(image_id):
     except Exception as e:
         db.session.rollback()
         return jsonify({'success': False, 'error': str(e)}), 500
+
+
+@bp.route('/init/database', methods=['GET'])
+def check_database_exists():
+    try:
+        if Listings.check_table_exists():
+            flash ("Database already exists, 'error")
+        else:
+            raise Exception('Schema exists but database does not')
+    except:
+        with open('sql-setup/init.sql', 'r') as file:
+            sql_commands = file.read().split(';')
+            for command in sql_commands:
+                if command.strip():
+                    db.session.execute(text(command))
+        
+        db.session.commit()
+        flash ("Database initialised", 'success')
diff --git a/app/models/listings.py b/app/models/listings.py
index 7c00b96..7d44f1a 100644
--- a/app/models/listings.py
+++ b/app/models/listings.py
@@ -80,3 +80,6 @@ class Listings(db.Model):
             'business_fair_cost': self.business_fair_cost
         }
         
+    @classmethod
+    def check_table_exists(cls):
+        return cls.get_top_listings(1)
diff --git a/docker-compose.yml b/docker-compose.yml
index 33e6fe1..8b59898 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -31,7 +31,6 @@ services:
       MYSQL_PASSWORD: ${DATABASE_PASSWORD}
     volumes:
       - mysql_data:/var/lib/mysql
-      - ./sql-setup/init.sql:/docker-entrypoint-initdb.d/init.sql
     networks:
       - network
 
-- 
GitLab