From b70ba6ae2e72827aa6de343881625ebd9cd85b57 Mon Sep 17 00:00:00 2001 From: Lucas Mathews Date: Wed, 29 May 2024 15:38:25 +0200 Subject: [PATCH] continue developing account page, start transaction pages --- .gitignore | 6 +- api.yml | 7 ++ .../__pycache__/connection.cpython-312.pyc | Bin 11009 -> 11341 bytes application/account.py | 104 +++++++++++++++++- application/connection.py | 12 +- application/dashboard.py | 61 ++++++---- application/new_transaction.py | 36 ++++++ application/session_data.json | 2 +- application/transaction.py | 36 ++++++ manager.py | 6 +- test_database.db | Bin 700416 -> 700416 bytes 11 files changed, 238 insertions(+), 32 deletions(-) create mode 100644 application/new_transaction.py create mode 100644 application/transaction.py diff --git a/.gitignore b/.gitignore index 9f08cb0..305b006 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ -flask_session +flask_session/ +application/__pycache__/ +application/session_data.json +bank.db +test_database.db # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/api.yml b/api.yml index fd0b010..1fd6357 100644 --- a/api.yml +++ b/api.yml @@ -301,6 +301,13 @@ paths: required: true schema: type: string + - name: otp_code + in: query + description: OTP to verify + required: true + schema: + type: integer + format: int32 - name: description in: query description: Account description diff --git a/application/__pycache__/connection.cpython-312.pyc b/application/__pycache__/connection.cpython-312.pyc index c0cd5f8dae809446072c82cf12800f37b18052c6..60315baf0536a6733ae9b675abf51f44488184e9 100644 GIT binary patch delta 1011 zcmb7CT}V_x6rQ=WckkVwySv`?Z`E~o)y*IJ5h^49($!3fQq(ey4B1i8%3@qIVNDGQ zdP!v{f~U?TsoEe zUel}yY(1-`YdnC^s&KZA#7S&yGPm(S0=10cHaoSBp*D@$03Fm0=%hN;I$d27b&Sbv z3DgOiPF;ZRL7eZNeEbVLjA^_q;bddTw2$B{V@`QP9EFg*?=8RxS~IEqOJcF{()OIB zeVf1t)oj@7dM|gJ@239x=7!yD55#To8_qeo99XGQ;Tku|hk-U5%Zay%S^+fQ%<4Ex zt(A|06Xvf|zvCKX*0Wc_b$m7b0pRtFa=8IEnei^87<*Zf2s8m~n;D3Bbu1+DiJ%Xg z_|u?|A4;)enSZi-Jm#o0Do3TLnaZUQ7A4>j@XXw+?c(#9dWuY~9c_(s!b@8 zv>i-cQS~mmC(S^`gkc2n(1E|>x6TdVhtcYWOR`xMLvjyxDPz+GRN(2?HWTJZfmebE${7E~?gpwvel#ZnWc0@YQc7($h`+Fno1c=w*>kC3v#BAv>Te<3If58j& zLqa7dfk7@Oj2w5Po5>-CJgm$LxzW5Lhg{0MT@l>vt7 zWGv-{FizyIf`%M1*Gx<_6XVOon68lS_DJ~pZHWzCK@j#Aw;C3Src$X76@CN*DY!;D*-XjD zNbN)~Sx7_=84=wFgI-D>qdyNlLukaB1L_io6@e41hfbbET zC1^5dpZ)>2{RBQJ5VWtUdpIQQ=lV+y&34KCn@5W?z*u3-bL`@k^Fu|E?CkAov9i>2 z45xk1wEe_d?Sg+=)pA5n+MhzVx$_{=CWlqP(b{%bJBf4T!{6{r=m>fv57j)$o8hqi zHWF2!fZw;>C%hgzq;*i>lKn2W50pWU{<;v{!=Hg9a9oQdTKNLaBCVndF0X~nj|V2y zGAlwwE5jstSrgg;$}20Rl(L5Nb!|1Oa7E-Z?Xi1-%PLgIQFh=&Gyx8rkH)ZNdk_Zk zMb+WFemVF*gE8SL>vz~)Wve&!GK5$7=9EFDj|pDU$Mv4g`v{D%QQpT!pvjLz$(%w* zu$ZiO9H(Md12%_hF`Eu!ELDyDX_w)&3Np|sp$owuSDi$$l&UkkiCiTgULlxZYpQ{t zV~x(QX5GB&eISD$Qze{>XR)$F-(|8J;hcj%XiMcxw&c)qK7GlpOK!&V9=?<{^EzJ! zAik_?d5@iFScU3;tqrPfc_kc`ur=adI3dTu#gHFwr2|?o1!wF>=>b(6AbQt!?aToL zu_M!!kbi(BcW=o>TXKVzoZgZzSp)bg6R$o?RIYBghhU8ZzzXx>uS{#rM$!jGSg&^z MZbS_zfaRu~zcz-by8r+H diff --git a/application/account.py b/application/account.py index 1aa245c..a321b95 100644 --- a/application/account.py +++ b/application/account.py @@ -1,9 +1,17 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Account Page + import tkinter as tk -from tkinter import ttk, messagebox -import customtkinter -from config import CONFIG -from connection import get_transactions, format_balance, get_account import sys +import os +import customtkinter +from tkinter import ttk, messagebox +from config import CONFIG +from connection import get_transactions, format_balance, get_account, generate_otp, update_account + +description_entry = None +notes_entry = None +otp_entry = None ################# ### Functions ### @@ -45,10 +53,10 @@ def display_account_info(account_id): messagebox.showerror("Error", "Could not fetch account details.") return account = account_info['data'] - print(account) if 'description' not in account: messagebox.showerror("Error", "Account description not found.") return + global account_description account_description = account['description'] fields = {'Account ID': account_id, 'Description': account_description, 'Balance': format_balance(account['balance']), 'Account Type': account['account_type']} for i, (key, value) in enumerate(fields.items()): @@ -57,6 +65,90 @@ def display_account_info(account_id): label_key.grid(row=0, column=i*2, sticky='w', padx=10) label_value.grid(row=0, column=i*2+1, sticky='w', padx=10) +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] + command = f"python application\\transaction.py {transaction_id} \"{selected_transaction['values'][4]}\"" + return_code = os.system(command) + if return_code != 0: + print(f"Error: The command failed with return code {return_code}") + except Exception as e: + print(f"Error: {e}") + +def add_transaction(): + """Open the add transaction dialog.""" + command = f"python application\\new_transaction.py {account_id}" + return_code = os.system(command) + if return_code != 0: + print(f"Error: The command failed with return code {return_code}") + +def edit_account_details(): + """Opens a new window for editing the account details.""" + global edit_window, otp_entry, description_entry, notes_entry + edit_window = customtkinter.CTkToplevel(root) + edit_window.title("Edit Account Details") + edit_window.iconbitmap("application/luxbank.ico") + edit_window.geometry("300x350") + edit_window.attributes('-topmost', True) + + description_label = customtkinter.CTkLabel(edit_window, text="Description: ") + description_entry = customtkinter.CTkEntry(edit_window) + description_label.pack() + description_entry.pack() + + notes_label = customtkinter.CTkLabel(edit_window, text="Notes: ") + notes_entry = customtkinter.CTkEntry(edit_window) + notes_label.pack() + notes_entry.pack() + + customtkinter.CTkLabel(edit_window, text=" ").pack() # Add space under the address box + + otp_button = customtkinter.CTkButton(edit_window, text="Get OTP Code", command=generate_otp) + otp_button.pack() + + otp_label = customtkinter.CTkLabel(edit_window, text="OTP Code: ") + otp_entry = customtkinter.CTkEntry(edit_window) + otp_label.pack() + otp_entry.pack() + + save_button = customtkinter.CTkButton(edit_window, text="Verify OTP and Save", command=save_details) + save_button.pack() + edit_window.lift() + +def save_details(): + """Saves the edited account details.""" + global edit_window, otp_entry, description_entry, notes_entry, account_description + description = description_entry.get() if description_entry.get() != '' else None + notes = notes_entry.get() if notes_entry.get() != '' else None + otp_code = otp_entry.get() if otp_entry.get() != '' else None + + if not otp_code: + messagebox.showerror("Error", "OTP code must be entered.") + return + + if not messagebox.askyesno("Confirmation", "Are you sure you want to save the changes?"): + return + + try: + result = update_account(account_id, otp_code, description, notes) + if 'success' in result and result['success']: + display_account_info(account_id) + messagebox.showinfo("Success", "Account details have been updated.") + root.title(f"Transactions for: {account_description}") # Update the window title + welcome_label.configure(text=f"Transactions for: {account_description}") + edit_window.destroy() + else: + if result['message'] == "Invalid OTP.": + messagebox.showerror("Error", "Invalid OTP code. Please try again.") + else: + messagebox.showerror("Error", f"Could not update account details: {result['message']}") + except Exception as e: + messagebox.showerror("Error", f"Could not update account details: {str(e)}") + + + ############## ### Layout ### ############## @@ -89,7 +181,7 @@ 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=request_otp) +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) diff --git a/application/connection.py b/application/connection.py index 6ee20f6..60a41d7 100644 --- a/application/connection.py +++ b/application/connection.py @@ -1,3 +1,6 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Connection Page + import requests from requests.models import Response from config import CONFIG @@ -124,12 +127,12 @@ def get_account(account_id): print(f"RequestException: {e}") return {'success': False, 'message': "Could not connect to the server. Please try again later."} -def update_account(account_id, description=None, notes=None): +def update_account(account_id, otp_code:int, description=None, notes=None): """Updates the account details for the given account_id.""" try: with open('application\\session_data.json', 'r') as f: session_data = json.load(f) - params = {'account_id': account_id} + params = {'account_id': account_id, 'otp_code': otp_code} if description is not None: params['description'] = description if notes is not None: @@ -137,6 +140,11 @@ def update_account(account_id, description=None, notes=None): response = requests.put(CONFIG["server"]["url"] + "/Account", cookies=session_data['session_cookie'], params=params) response.raise_for_status() return response.json() + except requests.exceptions.HTTPError as e: + if response.status_code == 400: + return {'success': False, 'message': "Invalid OTP."} + print(f"HTTPError: {e}") + return {'success': False, 'message': "Could not connect to the server. Please try again later."} except requests.exceptions.RequestException as e: print(f"RequestException: {e}") return {'success': False, 'message': "Could not connect to the server. Please try again later."} diff --git a/application/dashboard.py b/application/dashboard.py index 1e8b507..bf44a45 100644 --- a/application/dashboard.py +++ b/application/dashboard.py @@ -1,3 +1,6 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Dashboard Page + from tkinter import messagebox, ttk import customtkinter import json @@ -13,6 +16,9 @@ address_entry = None otp_entry = None frame = None +fields = [('Name', 'name'), ('Client ID', 'client_id'), ('Email', 'email'), ('Phone', 'phone_number'), + ('Address', 'address'), ('Account Opened', 'opening_timestamp')] + ################# ### Functions ### ################# @@ -42,6 +48,7 @@ def exit_application(): else: messagebox.showerror("Logout failed", json_response['message']) + def display_client_info(): """Displays the client's information on the dashboard.""" global frame @@ -50,7 +57,7 @@ def display_client_info(): widget.destroy() else: frame = customtkinter.CTkFrame(root) - frame.pack(anchor='w', side='left', padx=20, pady=20) + frame.grid(row=1, column=0, padx=20, pady=20) try: with open('application\\session_data.json', 'r') as f: @@ -59,12 +66,10 @@ def display_client_info(): client_info = get_client(client_id) if client_info.get('success'): client = client_info['data'] - fields = [('Name', 'name'), ('Client ID', 'client_id'), ('Email', 'email'), ('Phone', 'phone_number'), - ('Address', 'address'), ('Account Opened', 'opening_timestamp')] for i, (display_name, key) in enumerate(fields): value = client.get(key, 'N/A') label_key = customtkinter.CTkLabel(frame, text=f"{display_name}: ", font=("Helvetica", 14)) - label_value = customtkinter.CTkLabel(frame, text=value, font=("Helvetica", 14)) + label_value = customtkinter.CTkLabel(frame, text=value, font=("Helvetica", 14), anchor='w') label_key.grid(row=i, column=0, sticky='e') label_value.grid(row=i, column=1, sticky='w') else: @@ -73,16 +78,13 @@ def display_client_info(): except Exception as e: messagebox.showerror("Error", f"Could not retrieve client information: {e}") - edit_button = customtkinter.CTkButton(frame, text="Edit Details", command=edit_details) - edit_button.grid(row=len(fields), column=0, columnspan=2) - def edit_details(): """Opens a new window for editing client details.""" global edit_window, email_entry, phone_entry, address_entry, otp_entry edit_window = customtkinter.CTkToplevel(root) edit_window.title("Edit Details") - edit_window.geometry("300x350") edit_window.iconbitmap("application/luxbank.ico") + edit_window.geometry("300x350") edit_window.attributes('-topmost', True) email_label = customtkinter.CTkLabel(edit_window, text="Email: ") @@ -143,14 +145,15 @@ def save_details(): if result['message'] == "Invalid OTP.": messagebox.showerror("Error", "MFA details not correct. Please try again.") else: - messagebox.showerror("Update Failed", result.get('message', 'Unknown error')) + messagebox.showerror("Error", f"Could not update client details: {result['message']}") except Exception as e: - messagebox.showerror("Error", f"An error occurred: {e}") - + messagebox.showerror("Error", f"Could not update client details: {str(e)}") def populate_table(): """Populates the accounts table with client accounts.""" - try: + for row in table.get_children(): # Clear the table before populating it + table.delete(row) + try: with open('application\\session_data.json', 'r') as f: session_data = json.load(f) client_id = session_data['client_id'] @@ -178,6 +181,12 @@ def on_account_double_click(event): except Exception as e: print(f"Error: {e}") +def reload_info_and_accounts(): + """Reloads the client information and the accounts table.""" + display_client_info() + populate_table() + + ############## ### Layout ### ############## @@ -192,29 +201,41 @@ customtkinter.set_appearance_mode(CONFIG["preferences"].get("dark_theme", "light # Create a label for the title welcome_label = customtkinter.CTkLabel(root, text="Welcome to the Luxbank Dashboard!", font=("Helvetica", 24)) -welcome_label.pack(pady=20) - -display_client_info() +welcome_label.grid(row=0, column=0, columnspan=2, pady=20) # Create a frame for buttons button_frame = customtkinter.CTkFrame(root) -button_frame.pack(pady=15, side='top') +button_frame.grid(row=1, column=0, columnspan=2, sticky='ew', pady=15) # Create the OTP button otp_button = customtkinter.CTkButton(button_frame, text="Get OTP Code", command=generate_otp) -otp_button.pack(side='left', padx=5) +otp_button.grid(row=0, column=0, padx=5) + +# Create the reload button +reload_button = customtkinter.CTkButton(button_frame, text="Reload", command=reload_info_and_accounts) +reload_button.grid(row=0, column=1, padx=5) # Create the logout button logout_button = customtkinter.CTkButton(button_frame, text="Logout", command=logout) -logout_button.pack(side='left', padx=5) +logout_button.grid(row=0, column=2, padx=5) # Create the exit button exit_button = customtkinter.CTkButton(button_frame, text="Exit", command=exit_application) -exit_button.pack(side='left', padx=5) +exit_button.grid(row=0, column=3, padx=5) + +# Display client info after creating the buttons +frame = customtkinter.CTkFrame(root) +frame.grid(row=2, column=0, padx=20, pady=20, sticky='n') # Adjusted grid placement +display_client_info() # Create a frame for the table table_frame = ttk.Frame(root) -table_frame.pack(side='right', fill='both', expand=True) +table_frame.grid(row=2, column=1, sticky='nsew') # Adjusted grid placement + +# Configure the grid +root.grid_rowconfigure(2, weight=1) # Adjusted row index +root.grid_columnconfigure(1, weight=1) + # Create the table table = ttk.Treeview(table_frame, columns=('Name', 'Account ID', 'Balance', 'Account Type'), show='headings') diff --git a/application/new_transaction.py b/application/new_transaction.py new file mode 100644 index 0000000..330b0cd --- /dev/null +++ b/application/new_transaction.py @@ -0,0 +1,36 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System New Transaction Page + +import tkinter as tk +from tkinter import ttk, messagebox +import customtkinter +from config import CONFIG +from connection import +import sys + +################# +### Functions ### +################# + +def get_transactoin + + + +############## +### Layout ### +############## + +# Initialise the main window +root = customtkinter.CTk() +root.title(f"Transactions: {transaction_description}") +root.iconbitmap("application/luxbank.ico") +root.geometry("800x400") + +if CONFIG["preferences"]["dark_theme"] == "dark": # Check if dark mode is enabled + customtkinter.set_appearance_mode("dark") # Set the style for dark mode +else: + customtkinter.set_appearance_mode("light") # Set the style for light mode + +# 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 diff --git a/application/session_data.json b/application/session_data.json index 148fffb..7ff4dbb 100644 --- a/application/session_data.json +++ b/application/session_data.json @@ -1 +1 @@ -{"session_cookie": {"session": "CjjpiVUxx009emp27lUSxMpJ4Dt1sUi3fV-_VILXFrw"}, "client_id": "d18e5ae0"} \ No newline at end of file +{"session_cookie": {"session": "nwHUYOr9vg2nOaZmrYNmWgjMgJ47QLIz71_dX_kFH_o"}, "client_id": "d18e5ae0"} \ No newline at end of file diff --git a/application/transaction.py b/application/transaction.py new file mode 100644 index 0000000..0a549aa --- /dev/null +++ b/application/transaction.py @@ -0,0 +1,36 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Transaction Page + +import tkinter as tk +from tkinter import ttk, messagebox +import customtkinter +from config import CONFIG +from connection import get_transaction, format_balance +import sys + +################# +### Functions ### +################# + + + + + +############## +### Layout ### +############## + +# Initialise the main window +root = customtkinter.CTk() +root.title(f"Transactions: {transaction_description}") +root.iconbitmap("application/luxbank.ico") +root.geometry("800x400") + +if CONFIG["preferences"]["dark_theme"] == "dark": # Check if dark mode is enabled + customtkinter.set_appearance_mode("dark") # Set the style for dark mode +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.pack(pady=10) \ No newline at end of file diff --git a/manager.py b/manager.py index 9bd63eb..e75ca16 100644 --- a/manager.py +++ b/manager.py @@ -173,7 +173,7 @@ def get_client(client_id:str): return format_response(False, "Client not found."), 404 @login_required -def update_client(client_id: str, otp_code: int, **kwargs): +def update_client(client_id:str, otp_code:int, **kwargs): """Updates a client in the database. If the client is not found, returns an error message.""" current_client_id, is_admin = get_current_client() if not verify_otp(current_client_id, otp_code): @@ -260,9 +260,11 @@ def add_account(client_id:str, description:str, account_type:str, **kwargs): return format_response(True, f"New account has been added: account_id: {account_id}"), 200 @login_required -def update_account(account_id:str, **kwargs): +def update_account(account_id:str, otp_code:str, **kwargs): """Updates an account in the database. If the account is not found, returns an error message.""" current_client_id, is_admin = get_current_client() + if not verify_otp(current_client_id, otp_code): + return format_response(False, "Invalid OTP."), 400 account_owner = session.query(Account).filter_by(account_id=account_id).one_or_none().client_id if not is_admin and account_owner != current_client_id: return format_response(False, "You can only view your own client information."), 403 diff --git a/test_database.db b/test_database.db index 9be2bfb8ddb80e0a3f186f10d1979c2e18db50dc..7fe9cd29ad9f15aaaeb189ad9b1dd071a88663c8 100644 GIT binary patch delta 215 zcmV;|04V=};3|ONDv%oi3XvQ`0Sd8TroT*f004FmnGZP*)(&?L0S%K4HVnxOQVZP+ zc?%E&k0uu?FfMg9tW=nHU`25Wd;8QhXoe|tOQL1-2-U?`U0NMn8AZBlJAVXntlU6@Hg3yN00fx{5hR_3s&;*9i R1%}WDhR_Fw&S!!O1LP=(BYV-f| h?f=g+0x=U1GXpUT5VHa?8xXStF~|1*=Q$Z4008g@C)of1