diff --git a/api.yml b/api.yml index 048b9b1..7f28370 100644 --- a/api.yml +++ b/api.yml @@ -436,11 +436,20 @@ paths: required: false schema: type: string + - name: otp_code + in: query + description: OTP to verify + required: true + schema: + type: integer + format: int32 responses: '200': description: Successful operation '400': description: Invalid input + '403': + description: Invalid OTP '401': description: Insufficient funds '404': diff --git a/application/__pycache__/connection.cpython-312.pyc b/application/__pycache__/connection.cpython-312.pyc index f151d9a..7f0ee9e 100644 Binary files a/application/__pycache__/connection.cpython-312.pyc and b/application/__pycache__/connection.cpython-312.pyc differ diff --git a/application/account.py b/application/account.py index 9824782..b965fdb 100644 --- a/application/account.py +++ b/application/account.py @@ -20,7 +20,7 @@ notes_text = None if len(sys.argv) > 3: # Check if the account description is provided as a command line argument account_id = sys.argv[1] - account_description = sys.argv[3] + account_description = sys.argv[3] # This is passed so that the window can be titled appopriately else: messagebox.showerror("Error", "Account ID and description were not provided by the server.") sys.exit(1) @@ -71,8 +71,8 @@ def display_account_info(account_id): def on_transaction_double_click(event): """Handles double-click event on a transaction in the table.""" try: - selected_transaction = transactions_table.item(transactions_table.selection()) - transaction_id = selected_transaction['values'][0] + selected_transaction = transactions_table.item(transactions_table.selection()) + transaction_id = selected_transaction['values'][-1] command = f"python application\\transaction.py {transaction_id} \"{selected_transaction['values'][4]}\"" return_code = os.system(command) if return_code != 0: @@ -187,18 +187,24 @@ display_account_info(account_id) # Add buttons for adding a new transaction, requesting the OTP, and editing the account details button_frame = customtkinter.CTkFrame(root) button_frame.pack(fill=tk.X, pady=10) + add_transaction_button = customtkinter.CTkButton(button_frame, text="Add Transaction", command=add_transaction) add_transaction_button.grid(row=0, column=0, padx=10) + request_otp_button = customtkinter.CTkButton(button_frame, text="Request OTP", command=generate_otp) request_otp_button.grid(row=0, column=1, padx=10) + edit_account_details_button = customtkinter.CTkButton(button_frame, text="Edit Account Details", command=edit_account_details) edit_account_details_button.grid(row=0, column=2, padx=10) +close_button = customtkinter.CTkButton(button_frame, text="Close", command=root.destroy) +close_button.grid(row=0, column=3, padx=10) + table_frame = customtkinter.CTkFrame(root) table_frame.pack(fill=tk.BOTH, expand=True) # Create the transactions table -transactions_table = ttk.Treeview(table_frame, columns=("Description", "Transaction Type", "Amount", "Timestamp", "Sender", "Recipient", "Transaction ID"), show="headings") +transactions_table = ttk.Treeview(table_frame, columns=("Description", "Transaction Type", "Amount", "Timestamp", "Sender", "Recipient"), show="headings") transactions_table.pack(fill=tk.BOTH, expand=True) for col in transactions_table["columns"]: transactions_table.heading(col, text=col) @@ -210,7 +216,6 @@ transactions_table.column("Amount", width=20) transactions_table.column("Timestamp", width=75) transactions_table.column("Sender", width=20) transactions_table.column("Recipient", width=20) -transactions_table.column("Transaction ID", width=100) populate_transactions_table(transactions_table, account_id) diff --git a/application/app.ini b/application/app.ini index 903ac4c..1e175cc 100644 --- a/application/app.ini +++ b/application/app.ini @@ -8,6 +8,6 @@ dark_theme = dark theme = dark-blue [client] -default_id = 9ce7d233 +default_id = 9755c18f default_password = Happymeal1 diff --git a/application/connection.py b/application/connection.py index 0605f2c..784eb51 100644 --- a/application/connection.py +++ b/application/connection.py @@ -138,14 +138,14 @@ def get_transaction(transaction_id): session_data = json.load(f) response = requests.get(CONFIG["server"]["url"] + "/Transaction", cookies=session_data['session_cookie'], params={'transaction_id': transaction_id}) response.raise_for_status() - return response.json() # Return the JSON response directly + transaction = response.json() + if 'data' not in transaction or 'description' not in transaction['data']: + print(f"Error: The transaction dictionary does not have a 'data' key. Transaction: {transaction}") + return None + return transaction except requests.exceptions.RequestException as e: print(f"RequestException: {e}") - return None, "Could not connect to the server. Please try again later." - except ValueError as ve: - print(f"ValueError: {ve}") - return None, "Invalid JSON response from server." - + return {'success': False, 'message': "Could not connect to the server. Please try again later."} def update_account(account_id, otp_code:int, description=None, notes=None): """Updates the account details for the given account_id.""" @@ -169,13 +169,15 @@ def update_account(account_id, otp_code:int, description=None, notes=None): print(f"RequestException: {e}") return {'success': False, 'message': "Could not connect to the server. Please try again later."} -def new_transaction(account): +def new_transaction(ammount, account_id, recipient_account_id, otp_code, description): """Creates a new transaction for the given account.""" try: with open('application\\session_data.json', 'r') as f: session_data = json.load(f) - params = {'account_id': account['account_id']} - response = requests.post(CONFIG["server"]["url"] + "/Transaction/History", cookies=session_data['session_cookie'], params=params) + if description == "": + description = None + params = {"account_id": account_id, "recipient_account_id": recipient_account_id, "amount": ammount, "description": description, "otp_code": otp_code} + response = requests.post(CONFIG["server"]["url"] + "/Transaction", cookies=session_data['session_cookie'], params=params) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: diff --git a/application/dashboard.py b/application/dashboard.py index 8739ff9..da63840 100644 --- a/application/dashboard.py +++ b/application/dashboard.py @@ -84,7 +84,7 @@ def edit_details(): edit_window = customtkinter.CTkToplevel(root) edit_window.title("Edit Details") edit_window.iconbitmap("application/luxbank.ico") - edit_window.geometry("300x350") + edit_window.geometry("300x330") edit_window.attributes('-topmost', True) email_label = customtkinter.CTkLabel(edit_window, text="Email: ") @@ -112,7 +112,7 @@ def edit_details(): otp_label.pack() otp_entry.pack() - save_button = customtkinter.CTkButton(edit_window, text="Verify OTP and Save", command=change_password_save) + save_button = customtkinter.CTkButton(edit_window, text="Verify OTP and Save", command=save_details) save_button.pack() edit_window.lift() @@ -287,25 +287,29 @@ button_frame.grid(row=1, column=0, columnspan=2, sticky='ew', pady=15) otp_button = customtkinter.CTkButton(button_frame, text="Get OTP Code", command=generate_otp, width=100) otp_button.grid(row=0, column=0, padx=3) -# Create the OTP button +# Create the new transaction button transaction_button = customtkinter.CTkButton(button_frame, text="New Transaction", command=new_transaction, width=100) transaction_button.grid(row=0, column=1, padx=3) +# Create the edit client Details button +transaction_button = customtkinter.CTkButton(button_frame, text="Edit Details", command=edit_details, width=100) +transaction_button.grid(row=0, column=2, padx=3) + # Create reset password button password_button = customtkinter.CTkButton(button_frame, text="Reset Password", command=change_password_box, width=100) -password_button.grid(row=0, column=2, padx=3) +password_button.grid(row=0, column=3, padx=3) # Create the reload button reload_button = customtkinter.CTkButton(button_frame, text="Refresh", command=reload_info_and_accounts, width=50) -reload_button.grid(row=0, column=3, padx=3) +reload_button.grid(row=0, column=4, padx=3) # Create the logout button logout_button = customtkinter.CTkButton(button_frame, text="Logout", command=logout, width=50) -logout_button.grid(row=0, column=4, padx=3) +logout_button.grid(row=0, column=5, padx=3) # Create the exit button exit_button = customtkinter.CTkButton(button_frame, text="Exit", command=exit_application, width=50) -exit_button.grid(row=0, column=5, padx=3) +exit_button.grid(row=0, column=6, padx=3) # Display client info after creating the buttons frame = customtkinter.CTkFrame(root) diff --git a/application/new_transaction.py b/application/new_transaction.py index 330b0cd..89ed569 100644 --- a/application/new_transaction.py +++ b/application/new_transaction.py @@ -5,14 +5,19 @@ import tkinter as tk from tkinter import ttk, messagebox import customtkinter from config import CONFIG -from connection import +from connection import format_balance, get_account, new_transaction, generate_otp import sys ################# ### Functions ### ################# -def get_transactoin +if len(sys.argv) > 3: # Check if the account description is provided as a command line argument + account_id = sys.argv[1] + account_description = sys.argv[3] # This is passed so that the window can be titled appopriately +else: + messagebox.showerror("Error", "Account ID and description were not provided by the server.") + sys.exit(1) @@ -33,4 +38,6 @@ else: # Display main window title welcome_label = customtkinter.CTkLabel(root, text=f"Transactions for: {account_description}", font=("Helvetica", 24)) -welcome_label.pack(pady=10) \ No newline at end of file +welcome_label.pack(pady=10) + +root.mainloop() diff --git a/application/session_data.json b/application/session_data.json index 407fb53..376b7b1 100644 --- a/application/session_data.json +++ b/application/session_data.json @@ -1 +1 @@ -{"session_cookie": {"session": "m-RSGMraIhk3P9RQ5oN5CSbgUBYlFpe9P2aOxm6ayU8"}, "client_id": "9ce7d233"} \ No newline at end of file +{"session_cookie": {"session": "9pRNUPAL2SDrVihKeZcAAb_uw6GzpPm2uTHqrE8urjE"}, "client_id": "9755c18f"} \ No newline at end of file diff --git a/application/transaction.py b/application/transaction.py index 4912703..97d6171 100644 --- a/application/transaction.py +++ b/application/transaction.py @@ -12,49 +12,29 @@ import sys ### Functions ### ################# -if len(sys.argv) > 3: # Check if the transaction description is provided as a command line argument +if len(sys.argv) > 2: # Check if the transaction ID and client ID are provided as command line arguments transaction_id = sys.argv[1] - transaction_description = sys.argv[3] + client_id = sys.argv[2] else: - messagebox.showerror("Error", "Transaction ID and description were not provided by the server.") + messagebox.showerror("Error", "Transaction ID and Client ID were not provided by the server.") sys.exit(1) def display_transaction_info(transaction_id): """Display the transaction information for the given transaction ID.""" - response = get_transaction(transaction_id) # Fetch the transaction details - print(response) + response = get_transaction(transaction_id) if response is None or 'data' not in response: - messagebox.showerror("Error", "Transaction details not found.") + messagebox.showerror("Error", "Could not fetch account details.") return - - transaction = response.get('data', {}) # Get transaction data or empty dictionary - if not transaction: - messagebox.showerror("Error", "Transaction details not found.") + transaction = response.get('data',) + if "description" not in transaction: + messagebox.showerror("Error", "Transaction descroption not found.") return - global transaction_description - t_id = transaction['transaction_id'] - t_description = transaction['description'] - t_amount = format_balance(transaction['amount']) - t_timestamp = transaction['timestamp'] - t_sender = transaction['account_id'] - t_recipient = transaction['recipient_account_id'] - t_type = transaction['transaction_type'] - - # Create labels for each piece of transaction information and pack them into the transaction_frame - id_label = customtkinter.CTkLabel(transaction_frame, text=f"Transaction ID: {t_id}") - id_label.pack() - description_label = customtkinter.CTkLabel(transaction_frame, text=f"Description: {t_description}") - description_label.pack() - amount_label = customtkinter.CTkLabel(transaction_frame, text=f"Amount: {t_amount}") - amount_label.pack() - timestamp_label = customtkinter.CTkLabel(transaction_frame, text=f"Timestamp: {t_timestamp}") - timestamp_label.pack() - sender_label = customtkinter.CTkLabel(transaction_frame, text=f"Sender: {t_sender}") - sender_label.pack() - recipient_label = customtkinter.CTkLabel(transaction_frame, text=f"Recipient: {t_recipient}") - recipient_label.pack() - type_label = customtkinter.CTkLabel(transaction_frame, text=f"Transaction Type: {t_type}") - type_label.pack() + fields = {'Description': transaction['description'], 'Transaction Type ': transaction['transaction_type'], 'Amount': format_balance(transaction['amount']), 'Timestamp': transaction['timestamp'], 'Account ID': transaction['account_id'], 'Recipient ID': transaction['recipient_account_id'], "Transaction ID": transaction['transaction_id']} + for i, (key, value) in enumerate(fields.items()): + label = customtkinter.CTkLabel(transaction_frame, text=f"{key}:") + label.grid(row=i, column=0, sticky="w") + value_label = customtkinter.CTkLabel(transaction_frame, text=value) + value_label.grid(row=i, column=1, sticky="w") ############## @@ -63,9 +43,9 @@ def display_transaction_info(transaction_id): # Initialise the main window root = customtkinter.CTk() -root.title(f"Transactions: {transaction_description}") +root.title("Banking System Transaction Page") root.iconbitmap("application/luxbank.ico") -root.geometry("800x400") +root.geometry("370x300") # Create a close button at the top of the window close_button = customtkinter.CTkButton(root, text="Close", command=root.destroy) @@ -77,13 +57,13 @@ else: customtkinter.set_appearance_mode("light") # Set the style for light mode # Display main window title -welcome_label = customtkinter.CTkLabel(root, text=f"Transactions: {transaction_description}", font=("Helvetica", 24)) +welcome_label = customtkinter.CTkLabel(root, text="Transaction Details", font=("Helvetica", 24)) welcome_label.pack(pady=10) # Create a frame for the transaction details transaction_frame = customtkinter.CTkFrame(root) transaction_frame.pack(pady=10) + display_transaction_info(transaction_id) - -root.mainloop() \ No newline at end of file +root.mainloop() diff --git a/bank.db b/bank.db index 66dd2f2..2fe4842 100644 Binary files a/bank.db and b/bank.db differ diff --git a/bank.ini b/bank.ini index 65985bc..f430afc 100644 --- a/bank.ini +++ b/bank.ini @@ -1,5 +1,5 @@ [database] -name=test_database.db +name=bank.db [api_file] name=api.yml diff --git a/cli/cli.ini b/cli/cli.ini index 675afdc..9549bef 100644 --- a/cli/cli.ini +++ b/cli/cli.ini @@ -4,5 +4,5 @@ port = 81 url = http://127.0.0.1:81 [client] -default_id = 9ce7d233 -default_password = Happymeal1 \ No newline at end of file +default_id = +default_password = \ No newline at end of file diff --git a/manager.py b/manager.py index 6d3df8f..d875e7e 100644 --- a/manager.py +++ b/manager.py @@ -88,8 +88,6 @@ def clean_expired_otps(): delete_otp(client_id) otps_removed += 1 log_event(f"Cleaned {otps_removed} expired OTPs.") - - def log_event(data_to_log:str): """Logs an event to the log file.""" @@ -331,11 +329,14 @@ def get_transaction(transaction_id:int): return format_response(True, "", transaction.to_dict()), 200 @login_required -def add_transaction(amount:int, account_id, recipient_account_id, **kwargs): +def add_transaction(amount:int, account_id, recipient_account_id, otp_code:int, **kwargs): """Adds a new transaction to the database. If the account is not found, returns an error message.""" current_client_id, is_admin = get_current_client() if not is_admin and account_id != current_client_id: return format_response(False, "You can only view your own client information."), 403 + otp_verified = verify_otp(current_client_id, otp_code) # Verify if the OTP is correct + if not otp_verified: + return format_response(False, "Invalid OTP."), 400 transaction_id = generate_uuid() for account in session.query(Account).all(): if account.account_id == account_id: @@ -501,9 +502,10 @@ def initialise_database(password:str, email:str): """Initialises the database with an administrator client if no clients exist.""" existing_clients = session.query(Client).all() # Check if any clients exist in the database if not existing_clients: # If no clients exist, create an administrator client - add_client('ADMINISTRATOR', 'ADMINISTRATOR', 'ADMINISTRATOR', 'ADMINISTRATOR', email, password) # Add the administrator client + new_client = Client(generate_uuid_short(), "ADMIN", "ADMIN", timestamp(), "ADMIN", "ADMIN", email, hash_password(password), None, 1, 0, None) + session.add(new_client) session.commit() - admin_client = session.query(Client).filter_by(name='ADMINISTRATOR').one() # Retrieve the administrator client + admin_client = session.query(Client).filter_by(name='ADMIN').one() # Retrieve the administrator client admin_client.administrator = 1 # Set the new client as an administrator session.commit() log_event(f"Database initialised with administrator account with client_id {admin_client.client_id}.") diff --git a/test_database.db b/test_database.db deleted file mode 100644 index 02b4063..0000000 Binary files a/test_database.db and /dev/null differ diff --git a/test_database_generator.py b/test_database_generator.py index 910d81e..0f29094 100644 --- a/test_database_generator.py +++ b/test_database_generator.py @@ -44,7 +44,7 @@ def timestamp_this_century(): return random_date(start, end).strftime("%Y-%m-%d %H:%M:%S") -engine = create_engine('sqlite:///test_database.db') +engine = create_engine('sqlite:///bank.db') Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) session = Session()