From b95074d994503971a7b56d7f5f4128e8ca708813 Mon Sep 17 00:00:00 2001 From: DaanSelen Date: Mon, 2 Mar 2026 16:34:47 +0100 Subject: [PATCH] chore: try to make a working base --- .gitignore | 1 + docs/upgrade_database.md | 6 +++++ config.ini => src/config.ini | 1 - src/main.py | 15 ++++++++--- src/modules/config/reader.py | 8 +++--- src/modules/config/utilities.py | 15 ++++++++--- src/modules/database/database.py | 41 +++++++++++++++++++++++++++++ src/modules/database/schema.py | 23 ++++++++++++++++ src/modules/database/utilities.py | 42 +++++++++++++++++++++++++++--- src/modules/utilities/utilities.py | 39 +++++++++++++++++++++++++++ src/requirements.txt | 3 ++- 11 files changed, 178 insertions(+), 16 deletions(-) create mode 100644 docs/upgrade_database.md rename config.ini => src/config.ini (97%) create mode 100644 src/modules/utilities/utilities.py diff --git a/.gitignore b/.gitignore index b7faf40..54608a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.db # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] diff --git a/docs/upgrade_database.md b/docs/upgrade_database.md new file mode 100644 index 0000000..e933caa --- /dev/null +++ b/docs/upgrade_database.md @@ -0,0 +1,6 @@ +# Alembic + +Steps: +- alembic init alembic +- change alembic.ini (connection string) +- change env.py to include base diff --git a/config.ini b/src/config.ini similarity index 97% rename from config.ini rename to src/config.ini index 0ead698..4d1d8e7 100644 --- a/config.ini +++ b/src/config.ini @@ -29,7 +29,6 @@ totp_verified = false totp_key = ZSI2Z4QHSGVAK6TFVMZXORFDWKHDN4TQ [Other] -welcome_session = true [Database] type = sqlite diff --git a/src/main.py b/src/main.py index ff99939..796bc3d 100644 --- a/src/main.py +++ b/src/main.py @@ -2,13 +2,20 @@ import logging as log +import json + from modules.config.reader import reader +from modules.database.database import database +from modules.utilities.utilities import utilities as util if __name__ == '__main__': log.basicConfig(level=log.DEBUG) config_contents = reader.read_config() - log.info(config_contents) - input() - config_contents = reader.refresh_config(config_contents) - log.info(config_contents) \ No newline at end of file + + found, config_database = util.filter_config(config_contents, 'DATABASE') + if not found: + exit(1) + + ok, engine, session = database.create_session(config_database) + ok = database.verify_contents(engine) \ No newline at end of file diff --git a/src/modules/config/reader.py b/src/modules/config/reader.py index dcba80a..97d3c1d 100644 --- a/src/modules/config/reader.py +++ b/src/modules/config/reader.py @@ -2,23 +2,25 @@ import logging as log -from .utilities import preflight_checks +from .utilities import checks class reader(): + @staticmethod def read_config() -> dict: ''' check some basic things and then return the dict containing the config data ''' - ok, candidate_path = preflight_checks.search_known_paths() + ok, candidate_path = checks.search_known_paths() if not ok: return {} - ok, config_contents = preflight_checks.verify_contents(candidate_path) + ok, config_contents = checks.verify_contents(candidate_path) if not ok: return {} return config_contents + @staticmethod def refresh_config(config_contents: dict) -> dict: log.debug(f'refreshing config values') return reader.read_config() \ No newline at end of file diff --git a/src/modules/config/utilities.py b/src/modules/config/utilities.py index da909e1..25223d8 100644 --- a/src/modules/config/utilities.py +++ b/src/modules/config/utilities.py @@ -5,7 +5,8 @@ import logging as log import configparser as cp import os -class preflight_checks(): +class checks(): + @staticmethod def search_known_paths() -> tuple[bool, str]: ''' Look at predefined paths on the filesystem for a config file @@ -34,6 +35,7 @@ class preflight_checks(): log.error(f'error occured while searching for the config file: {err}') return False, '' + @staticmethod def verify_contents(config_path: str) -> tuple[bool, dict]: ''' Check the existing config file for contents @@ -53,10 +55,15 @@ class preflight_checks(): log.debug(f'checking integrity of section: {section}') if len(config.items(section)) == 0: - log.error('empty section, no keys or values') - return False, {} + log.warn('empty section, removing for runtime due to irrelevance') + config.remove_section(section) - return True, dict(config.items()) + config_dict = {} + for section in config.sections(): + items = dict(config.items(section)) + config_dict[section] = items + + return True, config_dict except cp.ParsingError as err: log.error(f'error parsing the ini config file: {err}') diff --git a/src/modules/database/database.py b/src/modules/database/database.py index 92bc4e7..70728df 100644 --- a/src/modules/database/database.py +++ b/src/modules/database/database.py @@ -1,2 +1,43 @@ #!/bin/env python3 +import logging as log + +import sqlalchemy +import sqlalchemy.orm + +from .schema import Base +from .utilities import checks + +class database(): + @staticmethod + def create_session(database_config: dict) -> tuple[bool, sqlalchemy.engine.Engine | None, sqlalchemy.orm.Session | None]: + ok, connection_string = checks.generate_connection_string(database_config) + if not ok: + return False, None, None + + log.info(connection_string) + + try: + engine = sqlalchemy.create_engine(connection_string, echo=False) + + local_session = sqlalchemy.orm.sessionmaker(bind=engine) + session = local_session() + + return True, engine, session + + except Exception as err: + log.critical(f'database initialization failed: {err}') + return False, None, None + + @staticmethod + def verify_contents(engine) -> bool: + try: + log.info('checking if all tables are present, and creating them if they are not') + + Base.metadata.create_all(engine) + + return True + + except Exception as err: + log.error('failed to verify contents of the database') + return False diff --git a/src/modules/database/schema.py b/src/modules/database/schema.py index 92bc4e7..b69f662 100644 --- a/src/modules/database/schema.py +++ b/src/modules/database/schema.py @@ -1,2 +1,25 @@ #!/bin/env python3 +import sqlalchemy +import sqlalchemy.orm + +Base = sqlalchemy.orm.declarative_base() + +class User(Base): + __tablename__ = 'users' + + user_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, index=True) + + username = sqlalchemy.Column(sqlalchemy.String, unique=True, index=True) + password = sqlalchemy.Column(sqlalchemy.String) + + role = sqlalchemy.Column(sqlalchemy.String, default='user') + + totp_enabled = sqlalchemy.Column(sqlalchemy.Boolean, default=False) + totp_verified = sqlalchemy.Column(sqlalchemy.Boolean, default=False) + totp_key = sqlalchemy.Column(sqlalchemy.String) + + email = sqlalchemy.Column(sqlalchemy.String) + +class Wireguard(Base): + __tablename__ = 'wireguard_interfaces' \ No newline at end of file diff --git a/src/modules/database/utilities.py b/src/modules/database/utilities.py index d992f48..b99eea9 100644 --- a/src/modules/database/utilities.py +++ b/src/modules/database/utilities.py @@ -1,9 +1,45 @@ #!/bin/env python3 -class db_setup(): +import logging as log + +import os +from urllib.parse import quote_plus + +from modules.utilities.utilities import utilities as util + +class checks(): ''' This class functions as a collection of function to prepare a connection to a database. ''' - def compile_connection_string() -> string: - return "The Connection string" \ No newline at end of file + @staticmethod + def generate_connection_string(database_config: dict) -> tuple[bool, str]: + if not 'type' in database_config: + return False, '' + + username = quote_plus(database_config.get('username', '')) + password = quote_plus(database_config.get('password', '')) + + match database_config['type']: + case 'sqlite': + local_database_path = os.path.abspath("./database") + + exists = util.ensure_directory(local_database_path) + if exists: + connection_string = f'sqlite:///{local_database_path}/wgdashboard.db' + else: + return False, '' + + case 'postgresql' | 'postgres': + connection_string = f'postgresql+psycopg://{username}:{password}@{database_config.get('host', 'localhost')}:{database_config.get('port', '5432')}' + + case 'mariadb': + connection_string = f'mariadb+mariadbconnector://{username}:{password}@{database_config.get('host', 'localhost')}:{database_config.get('port', '3306')}' + + case 'mysql': + connection_string = f'mysql+pymysql://{username}:{password}@{database_config.get('host', 'localhost')}:{database_config.get('port', '3306')}' + + case _: + return False, '' + + return True, connection_string \ No newline at end of file diff --git a/src/modules/utilities/utilities.py b/src/modules/utilities/utilities.py new file mode 100644 index 0000000..94912f9 --- /dev/null +++ b/src/modules/utilities/utilities.py @@ -0,0 +1,39 @@ +#!/bin/env python3 + +import logging as log + +import os + +class utilities(): + @staticmethod + def filter_config(config_contents: dict, filter_keyword: str) -> tuple[bool, dict]: + ''' + Helper function to grab a specific part of the config + ''' + + log.debug(f'searching for section: {filter_keyword}') + for section in config_contents: + if str(section).lower() == filter_keyword.lower(): + return True, dict(config_contents[section].items()) + + return False, {} + + @staticmethod + def ensure_directory(path: str) -> bool: + ''' + Make the directory if it does not exist yet, return only true if the directory was missing and created. + ''' + + log.debug(f'checking if the directory at: {path} exists') + if os.path.exists(path) and os.path.isdir(path): + return True + + try: + os.mkdir(path) + return True + + except Exception as err: + log.critical('failed to create directory') + return False + + \ No newline at end of file diff --git a/src/requirements.txt b/src/requirements.txt index 3b77bc9..6607c1e 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1 +1,2 @@ -configparser==7.2.0 \ No newline at end of file +configparser==7.2.0 +sqlalchemy==2.0.47 \ No newline at end of file