Implement transaction display and other small changes

This commit is contained in:
Lucas Mathews
2024-05-31 16:24:02 +02:00
parent 60114d6ef2
commit b6d798251f
15 changed files with 83 additions and 74 deletions

View File

@@ -436,11 +436,20 @@ paths:
required: false required: false
schema: schema:
type: string type: string
- name: otp_code
in: query
description: OTP to verify
required: true
schema:
type: integer
format: int32
responses: responses:
'200': '200':
description: Successful operation description: Successful operation
'400': '400':
description: Invalid input description: Invalid input
'403':
description: Invalid OTP
'401': '401':
description: Insufficient funds description: Insufficient funds
'404': '404':

View File

@@ -20,7 +20,7 @@ notes_text = None
if len(sys.argv) > 3: # Check if the account description is provided as a command line argument if len(sys.argv) > 3: # Check if the account description is provided as a command line argument
account_id = sys.argv[1] 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: else:
messagebox.showerror("Error", "Account ID and description were not provided by the server.") messagebox.showerror("Error", "Account ID and description were not provided by the server.")
sys.exit(1) sys.exit(1)
@@ -71,8 +71,8 @@ def display_account_info(account_id):
def on_transaction_double_click(event): def on_transaction_double_click(event):
"""Handles double-click event on a transaction in the table.""" """Handles double-click event on a transaction in the table."""
try: try:
selected_transaction = transactions_table.item(transactions_table.selection()) selected_transaction = transactions_table.item(transactions_table.selection())
transaction_id = selected_transaction['values'][0] transaction_id = selected_transaction['values'][-1]
command = f"python application\\transaction.py {transaction_id} \"{selected_transaction['values'][4]}\"" command = f"python application\\transaction.py {transaction_id} \"{selected_transaction['values'][4]}\""
return_code = os.system(command) return_code = os.system(command)
if return_code != 0: 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 # Add buttons for adding a new transaction, requesting the OTP, and editing the account details
button_frame = customtkinter.CTkFrame(root) button_frame = customtkinter.CTkFrame(root)
button_frame.pack(fill=tk.X, pady=10) button_frame.pack(fill=tk.X, pady=10)
add_transaction_button = customtkinter.CTkButton(button_frame, text="Add Transaction", command=add_transaction) add_transaction_button = customtkinter.CTkButton(button_frame, text="Add Transaction", command=add_transaction)
add_transaction_button.grid(row=0, column=0, padx=10) 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 = customtkinter.CTkButton(button_frame, text="Request OTP", command=generate_otp)
request_otp_button.grid(row=0, column=1, padx=10) 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 = customtkinter.CTkButton(button_frame, text="Edit Account Details", command=edit_account_details)
edit_account_details_button.grid(row=0, column=2, padx=10) 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 = customtkinter.CTkFrame(root)
table_frame.pack(fill=tk.BOTH, expand=True) table_frame.pack(fill=tk.BOTH, expand=True)
# Create the transactions table # 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) transactions_table.pack(fill=tk.BOTH, expand=True)
for col in transactions_table["columns"]: for col in transactions_table["columns"]:
transactions_table.heading(col, text=col) 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("Timestamp", width=75)
transactions_table.column("Sender", width=20) transactions_table.column("Sender", width=20)
transactions_table.column("Recipient", width=20) transactions_table.column("Recipient", width=20)
transactions_table.column("Transaction ID", width=100)
populate_transactions_table(transactions_table, account_id) populate_transactions_table(transactions_table, account_id)

View File

@@ -8,6 +8,6 @@ dark_theme = dark
theme = dark-blue theme = dark-blue
[client] [client]
default_id = 9ce7d233 default_id = 9755c18f
default_password = Happymeal1 default_password = Happymeal1

View File

@@ -138,14 +138,14 @@ def get_transaction(transaction_id):
session_data = json.load(f) session_data = json.load(f)
response = requests.get(CONFIG["server"]["url"] + "/Transaction", cookies=session_data['session_cookie'], params={'transaction_id': transaction_id}) response = requests.get(CONFIG["server"]["url"] + "/Transaction", cookies=session_data['session_cookie'], params={'transaction_id': transaction_id})
response.raise_for_status() 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: except requests.exceptions.RequestException as e:
print(f"RequestException: {e}") print(f"RequestException: {e}")
return None, "Could not connect to the server. Please try again later." return {'success': False, 'message': "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."
def update_account(account_id, otp_code:int, 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.""" """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}") print(f"RequestException: {e}")
return {'success': False, 'message': "Could not connect to the server. Please try again later."} 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.""" """Creates a new transaction for the given account."""
try: try:
with open('application\\session_data.json', 'r') as f: with open('application\\session_data.json', 'r') as f:
session_data = json.load(f) session_data = json.load(f)
params = {'account_id': account['account_id']} if description == "":
response = requests.post(CONFIG["server"]["url"] + "/Transaction/History", cookies=session_data['session_cookie'], params=params) 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() response.raise_for_status()
return response.json() return response.json()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:

View File

@@ -84,7 +84,7 @@ def edit_details():
edit_window = customtkinter.CTkToplevel(root) edit_window = customtkinter.CTkToplevel(root)
edit_window.title("Edit Details") edit_window.title("Edit Details")
edit_window.iconbitmap("application/luxbank.ico") edit_window.iconbitmap("application/luxbank.ico")
edit_window.geometry("300x350") edit_window.geometry("300x330")
edit_window.attributes('-topmost', True) edit_window.attributes('-topmost', True)
email_label = customtkinter.CTkLabel(edit_window, text="Email: ") email_label = customtkinter.CTkLabel(edit_window, text="Email: ")
@@ -112,7 +112,7 @@ def edit_details():
otp_label.pack() otp_label.pack()
otp_entry.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() save_button.pack()
edit_window.lift() 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 = customtkinter.CTkButton(button_frame, text="Get OTP Code", command=generate_otp, width=100)
otp_button.grid(row=0, column=0, padx=3) 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 = customtkinter.CTkButton(button_frame, text="New Transaction", command=new_transaction, width=100)
transaction_button.grid(row=0, column=1, padx=3) 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 # Create reset password button
password_button = customtkinter.CTkButton(button_frame, text="Reset Password", command=change_password_box, width=100) 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 # Create the reload button
reload_button = customtkinter.CTkButton(button_frame, text="Refresh", command=reload_info_and_accounts, width=50) 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 # Create the logout button
logout_button = customtkinter.CTkButton(button_frame, text="Logout", command=logout, width=50) 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 # Create the exit button
exit_button = customtkinter.CTkButton(button_frame, text="Exit", command=exit_application, width=50) 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 # Display client info after creating the buttons
frame = customtkinter.CTkFrame(root) frame = customtkinter.CTkFrame(root)

View File

@@ -5,14 +5,19 @@ import tkinter as tk
from tkinter import ttk, messagebox from tkinter import ttk, messagebox
import customtkinter import customtkinter
from config import CONFIG from config import CONFIG
from connection import from connection import format_balance, get_account, new_transaction, generate_otp
import sys import sys
################# #################
### Functions ### ### 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 # Display main window title
welcome_label = customtkinter.CTkLabel(root, text=f"Transactions for: {account_description}", font=("Helvetica", 24)) welcome_label = customtkinter.CTkLabel(root, text=f"Transactions for: {account_description}", font=("Helvetica", 24))
welcome_label.pack(pady=10) welcome_label.pack(pady=10)
root.mainloop()

View File

@@ -1 +1 @@
{"session_cookie": {"session": "m-RSGMraIhk3P9RQ5oN5CSbgUBYlFpe9P2aOxm6ayU8"}, "client_id": "9ce7d233"} {"session_cookie": {"session": "9pRNUPAL2SDrVihKeZcAAb_uw6GzpPm2uTHqrE8urjE"}, "client_id": "9755c18f"}

View File

@@ -12,49 +12,29 @@ import sys
### Functions ### ### 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_id = sys.argv[1]
transaction_description = sys.argv[3] client_id = sys.argv[2]
else: 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) sys.exit(1)
def display_transaction_info(transaction_id): def display_transaction_info(transaction_id):
"""Display the transaction information for the given transaction ID.""" """Display the transaction information for the given transaction ID."""
response = get_transaction(transaction_id) # Fetch the transaction details response = get_transaction(transaction_id)
print(response)
if response is None or 'data' not in response: 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 return
transaction = response.get('data',)
transaction = response.get('data', {}) # Get transaction data or empty dictionary if "description" not in transaction:
if not transaction: messagebox.showerror("Error", "Transaction descroption not found.")
messagebox.showerror("Error", "Transaction details not found.")
return return
global transaction_description 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']}
t_id = transaction['transaction_id'] for i, (key, value) in enumerate(fields.items()):
t_description = transaction['description'] label = customtkinter.CTkLabel(transaction_frame, text=f"{key}:")
t_amount = format_balance(transaction['amount']) label.grid(row=i, column=0, sticky="w")
t_timestamp = transaction['timestamp'] value_label = customtkinter.CTkLabel(transaction_frame, text=value)
t_sender = transaction['account_id'] value_label.grid(row=i, column=1, sticky="w")
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()
############## ##############
@@ -63,9 +43,9 @@ def display_transaction_info(transaction_id):
# Initialise the main window # Initialise the main window
root = customtkinter.CTk() root = customtkinter.CTk()
root.title(f"Transactions: {transaction_description}") root.title("Banking System Transaction Page")
root.iconbitmap("application/luxbank.ico") root.iconbitmap("application/luxbank.ico")
root.geometry("800x400") root.geometry("370x300")
# Create a close button at the top of the window # Create a close button at the top of the window
close_button = customtkinter.CTkButton(root, text="Close", command=root.destroy) 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 customtkinter.set_appearance_mode("light") # Set the style for light mode
# Display main window title # 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) welcome_label.pack(pady=10)
# Create a frame for the transaction details # Create a frame for the transaction details
transaction_frame = customtkinter.CTkFrame(root) transaction_frame = customtkinter.CTkFrame(root)
transaction_frame.pack(pady=10) transaction_frame.pack(pady=10)
display_transaction_info(transaction_id) display_transaction_info(transaction_id)
root.mainloop()
root.mainloop()

BIN
bank.db

Binary file not shown.

View File

@@ -1,5 +1,5 @@
[database] [database]
name=test_database.db name=bank.db
[api_file] [api_file]
name=api.yml name=api.yml

View File

@@ -4,5 +4,5 @@ port = 81
url = http://127.0.0.1:81 url = http://127.0.0.1:81
[client] [client]
default_id = 9ce7d233 default_id =
default_password = Happymeal1 default_password =

View File

@@ -88,8 +88,6 @@ def clean_expired_otps():
delete_otp(client_id) delete_otp(client_id)
otps_removed += 1 otps_removed += 1
log_event(f"Cleaned {otps_removed} expired OTPs.") log_event(f"Cleaned {otps_removed} expired OTPs.")
def log_event(data_to_log:str): def log_event(data_to_log:str):
"""Logs an event to the log file.""" """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 return format_response(True, "", transaction.to_dict()), 200
@login_required @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.""" """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() current_client_id, is_admin = get_current_client()
if not is_admin and account_id != current_client_id: if not is_admin and account_id != current_client_id:
return format_response(False, "You can only view your own client information."), 403 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() transaction_id = generate_uuid()
for account in session.query(Account).all(): for account in session.query(Account).all():
if account.account_id == account_id: 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.""" """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 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 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() 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 admin_client.administrator = 1 # Set the new client as an administrator
session.commit() session.commit()
log_event(f"Database initialised with administrator account with client_id {admin_client.client_id}.") log_event(f"Database initialised with administrator account with client_id {admin_client.client_id}.")

Binary file not shown.

View File

@@ -44,7 +44,7 @@ def timestamp_this_century():
return random_date(start, end).strftime("%Y-%m-%d %H:%M:%S") 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) Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine) Session = sessionmaker(bind=engine)
session = Session() session = Session()