diff --git a/.github/workflows/stage_unit_tests.yml b/.github/workflows/stage_unit_tests.yml index d8d4846..91fab72 100644 --- a/.github/workflows/stage_unit_tests.yml +++ b/.github/workflows/stage_unit_tests.yml @@ -1,9 +1,11 @@ name: Unit Tests - Stage Branch on: - push: + pull_request: + types: [opened , reopene, edited] branches: - - stage + - 'stage' + workflow_dispatch: # Allows manual triggering from the UI jobs: diff --git a/pyproject.toml b/pyproject.toml index 0d8897a..b2ff274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,3 +16,4 @@ pythonpath = ["."] testspaths = ["tests"] python_files = "test_*.py" python_functions = "test_*" + diff --git a/src/config.py b/src/config.py index d39aacf..02db6f5 100644 --- a/src/config.py +++ b/src/config.py @@ -1,20 +1,20 @@ import os import yaml -class VerityConfig: +class VerityConfig: def __init__(self): - self.SECRET_KEY = os.environ.get('SECRET_KEY') or 'super_secure_secret_key' - self.DATABASE = 'Verity.db' - self.CONFIG_FILE_DIRECTORY = 'config_files' - self.LOGGING_CONFIG = self.load_config_file('logging_config.yaml') - self.DATABASE_SCHEMA = self.load_config_file('verity_schema.yaml') + self.SECRET_KEY = os.environ.get("SECRET_KEY") or "super_secret_key" + self.DATABASE = "Verity.db" + self.CONFIG_FILE_DIRECTORY = "config_files" + self.LOGGING_CONFIG = self.load_config_file("logging_config.yaml") + self.DATABASE_SCHEMA = self.load_config_file("verity_schema.yaml") def load_config_file(self, file): - config = '' - filepath = os.path.join(self.CONFIG_FILE_DIRECTORY,file) + config = "" + filepath = os.path.join(self.CONFIG_FILE_DIRECTORY, file) try: - with open(filepath, 'r') as f: + with open(filepath, "r") as f: try: config = yaml.safe_load(f) except yaml.YAMLError as e: diff --git a/src/config_files/logging_config.yaml b/src/config_files/logging_config.yaml index 3d8b5b4..6af3902 100644 --- a/src/config_files/logging_config.yaml +++ b/src/config_files/logging_config.yaml @@ -11,7 +11,7 @@ formatters: handlers: stderr: class: logging.StreamHandler - level: INFO + level: DEBUG formatter: nodate stream: ext://sys.stdout file: diff --git a/src/data_handler.py b/src/data_handler.py index 825c8da..9a8a25f 100644 --- a/src/data_handler.py +++ b/src/data_handler.py @@ -1,48 +1,76 @@ import sqlite3 import logging from config import VerityConfig +from datetime import datetime logger = logging.getLogger(__name__) -class database(): - 'basic Database class to start some development' - def __init__(self, config: VerityConfig ) -> None: - self.database = config.DATABASE - self.schema = config.DATABASE_SCHEMA + +class database: + "basic Database class to start some development" + + def __init__(self) -> None: + self.verity_config = VerityConfig() + self.database = self.verity_config.DATABASE + self.schema = self.verity_config.DATABASE_SCHEMA + + def execute_sql(self, sql_statement: str, return_id: bool = False) -> (bool, int): + "send the query here, returns true if successful, false if fail" + logging.debug(f"received request to execute {sql_statement}") + new_id: int = 0 + is_success: bool = False + try: + connection = sqlite3.connect( + database=self.database, + timeout=10, # seconds i hope + ) + logging.debug("opened connection to database") + cursor = connection.cursor() + cursor.execute(sql=sql_statement) + connection.commit() + if return_id: + new_id = cursor.lastrowid + is_success = True + except Exception as e: # TODO: better Exception handling + logging.error(e) + is_success = False + finally: + connection.close() + return (is_success, new_id) @staticmethod - def _build_column(column:dict) -> str: - name = column['column_name'] - is_pk = column['is_pk'] - datatype = column['datatype'] - nullable = column['nullable'] - column_string = f'{name} {datatype}' + def _build_column(column: dict) -> str: + name = column["column_name"] + is_pk = column["is_pk"] + datatype = column["datatype"] + nullable = column["nullable"] + column_string = f"{name} {datatype}" if is_pk: - column_string += ' PRIMARY KEY AUTOINCREMENT' + column_string += " PRIMARY KEY AUTOINCREMENT" if not nullable: - column_string += ' NOT NULL' + column_string += " NOT NULL" return column_string @staticmethod - def _build_foreign_key(key:dict) -> str: - column = key['column'] - reference_table = key['references'] - reference_column = key['reference_column'] - return f'FOREIGN KEY ({column}) REFERENCES {reference_table} ({reference_column})' - - def _add_table_to_db(self, table:dict) -> bool: - 'Creates the table in the verity database, based on the schema yaml' - sql = f'''CREATE TABLE IF NOT EXISTS {table['table_name']} ( - ''' + def _build_foreign_key(key: dict) -> str: + column = key["column"] + ref_table = key["references"] + ref_column = key["reference_column"] + return f"FOREIGN KEY ({column}) REFERENCES {ref_table} ({ref_column})" + + def _add_table_to_db(self, table: dict) -> bool: + "Creates the table in the verity database, based on the schema yaml" + sql = f"""CREATE TABLE IF NOT EXISTS {table["table_name"]} ( + """ columns = [] - for column in table['table_columns']: + for column in table["table_columns"]: columns.append(self._build_column(column)) - sql += ',\n'.join(columns) - if table.get('table_foreign_keys', None): - sql += ',\n' - for key in table['table_foreign_keys']: - sql += f'{self._build_foreign_key(key)}' - sql += '\n);' + sql += ",\n".join(columns) + if table.get("table_foreign_keys", None): + sql += ",\n" + for key in table["table_foreign_keys"]: + sql += f"{self._build_foreign_key(key)}" + sql += "\n);" success_status = False try: connection = sqlite3.connect(self.database) @@ -64,14 +92,15 @@ def _add_table_to_db(self, table:dict) -> bool: return success_status def build_database(self): - for table in self.schema['tables']: + for table in self.schema["tables"]: logger.info(f"Checking {table['table_name']}") # Add true/false handling here to gracefully handle errors self._add_table_to_db(table) def print_table_schema(self, table_name): """ - Connects to the sqlite3 database, retrieves the schema of a specified table, + Connects to the sqlite3 database, + retrieves the schema of a specified table, and prints it to the console. Args: @@ -87,7 +116,9 @@ def print_table_schema(self, table_name): # Print the schema logger.debug(f"Schema for table: {table_name}") for row in cursor.fetchall(): - logger.debug(f" Column Name: {row[1]}, Data Type: {row[2]}, Not Null: {row[3]}") + logger.debug( + f"Column Name: {row[1]}, Data Type: {row[2]}, Not Null: {row[3]}" + ) except Exception as e: logger.error(f"An error occurred: {e}") @@ -96,4 +127,46 @@ def print_table_schema(self, table_name): try: connection.close() except Exception as e: - logger.error(e) \ No newline at end of file + logger.error(e) + + def add_budget_name(self, budget_name: str) -> int: + "takes budget name string, returns budget id" + now = datetime.now() + formatted_datetime = now.strftime("%Y-%m-%d %H:%M:%S") + logger.debug( + f"attempting to insert values into budget table {budget_name} | { + formatted_datetime + }" + ) + try: + connection = sqlite3.connect(self.database) + logger.debug("connection to db open") + cursor = connection.cursor() + logger.debug("cursor activated") + cursor.execute( + """INSERT INTO budget ( + name, + created_date + ) + VALUES (?,?) + """, + (budget_name, formatted_datetime), + ) + logger.debug("cursor executed") + connection.commit() + budget_id = cursor.lastrowid + logger.debug(f"insert attempt seems successful, budget id is {budget_id}") + + if budget_id is None: + budget_id = 0 + except Exception as e: + logger.error(f"Failed to insert budget name, error: {e}") + budget_id = 0 + finally: + try: + connection.close() + logger.debug("connection to db closed") + return budget_id + except Exception as e: + logger.error(f"failed to close connection message: {e}") + return 0 diff --git a/src/front/home.py b/src/front/home.py index e8da2eb..6b1750f 100644 --- a/src/front/home.py +++ b/src/front/home.py @@ -1,15 +1,36 @@ -from flask import Blueprint, render_template, flash, redirect, url_for, session -import os +from flask import Blueprint, render_template +from flask import flash, redirect, url_for, session, request +import logging -home_bp = Blueprint('home',__name__, template_folder='templates') +import data_handler -@home_bp.route('/') +logger = logging.getLogger(__name__) + +home_bp = Blueprint("home", __name__, template_folder="templates") + + +@home_bp.route("/") def home_page(): - cwd = os.getcwd() - return render_template('home.html',cwd=cwd) - -@home_bp.route('/clear_session') -def clear_session(): - session.clear() - flash('All Clear!',category='success') - return redirect(url_for('home.home')) + logger.info("home page hit") + return render_template("home.html") + + +@home_bp.route("/submit", methods=["POST"]) +def submit_budget_name(): + budget_name = request.form.get("budgetName") + logger.debug(request.form) + if budget_name: + logger.info(f"User submitted new budget name: {budget_name}") + session["budget_name"] = budget_name + db_call = data_handler.database() + budget_id = db_call.add_budget_name(budget_name) + if budget_id == 0: + # db entry failed, throw error message + flash("Budget Name not saved, please check the logs", "error") + return redirect(url_for("home.home_page")) + session["budget_id"] = budget_id + flash("Budget name saved!", "success") + return redirect(url_for("home.home_page")) + else: + flash("Please enter a budget name.", "error") + return redirect(url_for("home.home_page")) diff --git a/src/front/static/style.css b/src/front/static/verity_style.css similarity index 100% rename from src/front/static/style.css rename to src/front/static/verity_style.css diff --git a/src/front/templates/base.html b/src/front/templates/base.html index 918789f..dffef7b 100644 --- a/src/front/templates/base.html +++ b/src/front/templates/base.html @@ -13,7 +13,11 @@ crossorigin="anonymous" /> - +
diff --git a/src/front/templates/home.html b/src/front/templates/home.html index 5ddacd4..2f6b66f 100644 --- a/src/front/templates/home.html +++ b/src/front/templates/home.html @@ -1,5 +1,19 @@ {% extends "base.html" %} {% block title %} Home {% endblock %} {% block content %}Current Directory: {{ cwd }}
+