diff --git a/.github/workflows/stage_unit_tests.yml b/.github/workflows/stage_unit_tests.yml index d8d4846..e5f8769 100644 --- a/.github/workflows/stage_unit_tests.yml +++ b/.github/workflows/stage_unit_tests.yml @@ -1,13 +1,15 @@ 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: - pydantic_tests: + Stage_Pull_Request_Unit_Tests: runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 0000000..a5b4256 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,31 @@ +## Pull Request Type: + +**1. Title:** [Concise and Descriptive Title - Explain the Change] + + * Example: "Fix: Incorrect Calculation in User Profile" + * Example: "Feature: Add Support for Dark Mode" + * Keep titles short and informative. A good title should immediately convey the essence of the change. + +**2. Description:** + + * **Summary:** Briefly describe the purpose of this pull request in 1-2 sentences. + * **Motivation:** Explain *why* this change is needed. What problem does it solve? What opportunity does it address? Provide context. + * **Proposed Solution:** Describe your solution in detail. Explain *how* you implemented the change. Be specific. + * **Screenshots/GIFs (if applicable):** If your change involves UI changes, include screenshots or a GIF to demonstrate the impact. + * **Testing:** Describe the tests you've added or run to ensure the change is correct. (e.g., "Added unit tests for the new function," "Manually tested on Chrome, Firefox, and Safari"). + * **Known Limitations/Future Considerations:** Are there any known issues with this change? Are there any potential future improvements you envision? + +**3. Checklist:** + + [ ] I have followed the project's coding style and conventions. + [ ] I have written unit tests to cover my changes. + [ ] My code has been tested locally. + [ ] I have included screenshots/GIFs if applicable. + [ ] I have updated the documentation (if necessary). + [ ] I have labeled this PR appropriately (e.g., "bug," "feature," "refactor"). + + +**4. Reviewer Notes (Optional - Add if you have specific requests)** + + * "Please pay particular attention to the [specific section] of the code." + * "Would appreciate feedback on the design of this component." 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..16eb705 100644 --- a/src/data_handler.py +++ b/src/data_handler.py @@ -1,48 +1,98 @@ 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 + Will need a proper refactor once basic functions are in and working + This is POC + """ + + def __init__(self, config) -> None: + self.verity_config = config + self.schema = self.verity_config.DATABASE_SCHEMA + self.database = self.verity_config.DATABASE + + 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_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() + if return_id: + return (is_success, new_id) + else: + return is_success + + def read_database(self, sql_statement: str): + "reads the database query and returns the results" + logging.debug(f"received request to read {sql_statement}") + results = 0 + try: + connection = sqlite3.connect(self.database) + logging.debug("opened connection to database") + cursor = connection.cursor() + results = cursor.execute(sql_statement, {}) + results = results.fetchall() + logging.debug(f"query returned: {results}") + except Exception as e: + logging.error(f"Read error occured: {e}") + finally: + connection.close() + return results @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 +114,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 +138,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 +149,51 @@ 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 + + def get_budgets(self) -> list: + get_budget_sql = "SELECT name FROM budget" + budgets = self.read_database(get_budget_sql) + return budgets diff --git a/src/front/home.py b/src/front/home.py index e8da2eb..5f0a360 100644 --- a/src/front/home.py +++ b/src/front/home.py @@ -1,15 +1,40 @@ -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 +from config import VerityConfig -@home_bp.route('/') +logger = logging.getLogger(__name__) + +home_bp = Blueprint("home", __name__, template_folder="templates") +verity_config = VerityConfig() + + +@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") + db_call = data_handler.database(verity_config) + budgets = db_call.get_budgets() + return render_template("home.html", budgets=budgets) + + +@home_bp.route("/submit", methods=["POST"]) +def submit_budget_name(): + budget_name = request.form.get("budgetName") + logger.debug(f"Request Received: {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(verity_config) + 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..633fc07 100644 --- a/src/front/templates/home.html +++ b/src/front/templates/home.html @@ -1,5 +1,25 @@ {% extends "base.html" %} {% block title %} Home {% endblock %} {% block content %}Current Directory: {{ cwd }}
+
+ {% for budget in budgets %}
+- {{ budget[0] }}
+ {% endfor %}
+