Minor changes and added descriptions to all funcitons

This commit is contained in:
Lucas Mathews
2024-05-28 11:03:44 +02:00
parent 0732b26af3
commit 7b6ebbd0d2
12 changed files with 132 additions and 87 deletions

View File

@@ -22,25 +22,31 @@ otps = {} # Temporary dictionary to store OTPs and their creation time
### System ###
##############
def timestamp(): # Returns the current timestamp
def timestamp():
"""Returns the current timestamp in the format 'YYYY-MM-DD HH:MM:SS'."""
return (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
def hash_password(password:str): # Converts a string to SHA512 hash
def hash_password(password:str):
"""Hashes a password using the SHA-512 algorithm and returns the hexadecimal representation of the hash."""
return hashlib.sha512(password.encode()).hexdigest()
def generate_uuid(): # Generates a unique identifier for transactions
def generate_uuid():
"""Generates a unique identifier using the UUID4 algorithm and returns it as a string."""
return str(uuid.uuid4())
def generate_uuid_short(): # Generates a short uuid
def generate_uuid_short():
"""Generates a short unique identifier using the UUID4 algorithm and returns the first 8 characters as a string."""
return str(uuid.uuid4())[:8]
def get_email(client_id:str): # Returns the email of a client
def get_email(client_id:str):
"""Returns the email of a client given their client_id. If the client is not found, returns None."""
for client in session.query(Client).all():
if client.client_id == client_id:
return client.email
return None
def format_response(success: bool, message: str = '', data: dict = None): # Formats the response for the API so that it is standardised across all functions
def format_response(success: bool, message: str = '', data: dict = None):
"""Formats the response for the API so that it is standardised across all functions."""
response = {
'success': success,
'message': message,
@@ -50,22 +56,28 @@ def format_response(success: bool, message: str = '', data: dict = None): # Form
return jsonify(response)
def get_current_client():
"""Returns the client_id and administrator status of the currently logged in client. If no client is logged in, returns None, None."""
if 'client_id' not in flask_session:
return None, None
client = flask_session['client_id']
client_obj = session.query(Client).filter_by(client_id=client).one_or_none()
if client_obj is None:
return None, None
return client_obj.client_id, client_obj.administrator
def verify_otp(client_id:str, otp:int): # Verifies a one time password for a client
def verify_otp(client_id:str, otp:int):
"""Verifies a one time password for a client. Returns True if the OTP is correct and False otherwise."""
if client_id in otps and otps[client_id][0] == otp:
return True
return False
def delete_otp(client_id:str): # Deletes a one time password for a client
def delete_otp(client_id:str):
"""Deletes the OTP for a client."""
if client_id in otps:
del otps[client_id]
def check_expired_otps(): # Checks and deletes expired OTPs
def check_expired_otps():
"""Checks for expired OTPs and deletes them. An OTP is considered expired if it is older than 5 minutes."""
current_time = time.time()
expired_otps = [client_id for client_id, (otp, creation_time) in otps.items() if current_time - creation_time > 300] # Find OTPs older than 5 minutes
for client_id in expired_otps:
@@ -75,7 +87,8 @@ def check_expired_otps(): # Checks and deletes expired OTPs
### Authentication ###
######################
def login(client_id:str, password:str): # Logs in a user
def login(client_id:str, password:str):
"""Logs in a client using their client_id and password. Returns a success message if the login is successful and an error message otherwise."""
password_hash = hash_password(password)
for client in session.query(Client).all():
if client.client_id == client_id and client.hash == password_hash:
@@ -84,18 +97,21 @@ def login(client_id:str, password:str): # Logs in a user
return format_response(False, "Invalid client_id or password."), 400
def logout():
"""Logs out a client. Returns a success message if the logout is successful and an error message otherwise."""
if 'client_id' in flask_session:
flask_session.pop('client_id', None)
return format_response(True, "Logged out successfully."), 200
return format_response(False, "Not logged in."), 400
def status():
"""Returns the current status of the client."""
if 'client_id' in flask_session:
return format_response(True, f"Logged in as {flask_session['client_id']}"), 200
else:
return format_response(False, "Not logged in."), 400
def login_required(f):
"""Decorator function to check if a client is logged in before accessing a route."""
@wraps(f)
def decorated_function(*args, **kwargs):
if 'client_id' not in flask_session:
@@ -104,6 +120,7 @@ def login_required(f):
return decorated_function
def admin_required(f):
"""Decorator function to check if a client is an administrator before accessing a route."""
@wraps(f)
def decorated_function(*args, **kwargs):
if 'client_id' not in flask_session:
@@ -116,7 +133,8 @@ def admin_required(f):
return decorated_function
@login_required
def generate_otp(client_id:str): # Generates a one time password for a client
def generate_otp(client_id:str):
"""Generates a one time password for a client and sends it to their email address. Returns a success message if the OTP is generated and an error message otherwise."""
current_client_id, is_admin = get_current_client()
if not is_admin and client_id != current_client_id:
return format_response(False, "You can only generate OTPs for your own client account."), 403
@@ -133,7 +151,8 @@ def generate_otp(client_id:str): # Generates a one time password for a client
##############
@login_required
def get_client(client_id:str): # Returns a specific client in the database
def get_client(client_id:str):
"""Returns a specific client in the database. If the client is not found, returns an error message."""
current_client_id, is_admin = get_current_client()
if not is_admin and client_id != current_client_id:
return format_response(False, "You can only view your own client information."), 403
@@ -143,7 +162,8 @@ def get_client(client_id:str): # Returns a specific client in the database
return format_response(False, "Client not found."), 404
@login_required
def update_client(client_id:str, **kwargs): # Updates a client in the database
def update_client(client_id:str, **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 is_admin and client_id != current_client_id:
return format_response(False, "You can only view your own client information."), 403
@@ -172,7 +192,8 @@ def update_client(client_id:str, **kwargs): # Updates a client in the database
return format_response(False, "Client not found."), 404
@login_required
def change_password(client_id:str, password:str, new_password:str, otp:int): # Changes the password of a client
def change_password(client_id:str, password:str, new_password:str, otp:int):
"""Changes the password for 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 is_admin and client_id != current_client_id:
return format_response(False, "You can only update your own client information."), 403
@@ -192,9 +213,9 @@ def change_password(client_id:str, password:str, new_password:str, otp:int): # C
@login_required
def get_accounts(client_id: str):
"""Returns all accounts for a specific client in the database. If the client is not found, returns an error message."""
current_client_id, is_admin = get_current_client()
if current_client_id is None:
# return an appropriate response or raise an exception
raise Exception("No current client found")
if not is_admin and client_id != current_client_id:
return format_response(False, "You can only view your own client information."), 403
@@ -206,7 +227,7 @@ def get_accounts(client_id: str):
###############
@login_required
def get_account(account_id:str): # Returns a specific account in the database
def get_account(account_id:str):
current_client_id, is_admin = get_current_client()
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:
@@ -218,7 +239,8 @@ def get_account(account_id:str): # Returns a specific account in the database
return format_response(False, "Account not found."), 404
@login_required
def add_account(client_id:str, description:str, account_type:str, **kwargs): # Adds a new account to the database
def add_account(client_id:str, description:str, account_type:str, **kwargs):
"""Adds a new account to the database. If the client is not found, returns an error message."""
current_client_id, is_admin = get_current_client()
if not is_admin and client_id != current_client_id:
return format_response(False, "You can only view your own client information."), 403
@@ -237,7 +259,8 @@ def add_account(client_id:str, description:str, account_type:str, **kwargs): # A
return format_response(True, f"New account has been added: account_id: {account_id}"), 200
@login_required
def update_account(account_id:str, **kwargs): # Updates an account in the database
def update_account(account_id: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()
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:
@@ -268,7 +291,8 @@ def update_account(account_id:str, **kwargs): # Updates an account in the databa
###################
@login_required
def get_transaction(transaction_id:int): # Returns a specific transaction in the database
def get_transaction(transaction_id:int):
"""Returns a specific transaction in the database. If the transaction is not found, returns an error message."""
current_client_id, is_admin = get_current_client()
transaction = session.query(Transaction).filter_by(transaction_id=transaction_id).one_or_none()
if not transaction:
@@ -280,7 +304,8 @@ def get_transaction(transaction_id:int): # Returns a specific transaction in the
return format_response(True, "", transaction.to_dict()), 200
@login_required
def add_transaction(amount:int, account_id, recipient_account_id, **kwargs): # Adds a new transaction to the database
def add_transaction(amount:int, account_id, recipient_account_id, **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
@@ -290,23 +315,21 @@ def add_transaction(amount:int, account_id, recipient_account_id, **kwargs): # A
account_from = account
if account.account_id == recipient_account_id:
account_dest = account
# Check if account has enough funds
if account_from.balance < amount:
if account_from.balance < amount: # Check if account has enough funds
return format_response(False, "Insufficient funds."), 400
# Perform the transaction
account_from.balance -= amount
account_from.balance -= amount # Perform the transaction
account_dest.balance += amount
transaction_type = "transfer"
session.commit()
# Create the transaction record
description = kwargs.get("description", None)
description = kwargs.get("description", None) # Create the transaction record
new_transaction = Transaction(transaction_id, transaction_type, amount, timestamp(), description, account_id, recipient_account_id)
session.add(new_transaction)
session.commit()
return format_response(True, f"New transaction has been added: transaction_id: {transaction_id}"), 200
@login_required
def transaction_history(account_id:int): # Returns all transactions for a specific account
def transaction_history(account_id:int):
"""Returns all transactions for a specific account in the database. If the account is not found, returns an error message."""
current_client_id, is_admin = get_current_client()
account = session.query(Account).filter_by(account_id=account_id).one_or_none()
if not account:
@@ -321,10 +344,10 @@ def transaction_history(account_id:int): # Returns all transactions for a specif
#####################
@admin_required
def delete_client(client_id:str): # Deletes a client from the database
def delete_client(client_id:str):
"""Deletes a client from the database. If the client is not found, returns an error message."""
if client_id == flask_session['client_id']:
return format_response(False, "You cannot delete your own account."), 400
for client in session.query(Client).all():
if client.client_id == client_id:
if client.accounts == None:
@@ -336,7 +359,8 @@ def delete_client(client_id:str): # Deletes a client from the database
return format_response(False, "Client not found."), 404
@admin_required
def delete_account(account_id:str): # Deletes an account from the database
def delete_account(account_id:str):
"""Deletes an account from the database. If the account is not found, returns an error message."""
for account in session.query(Account).all():
if account.account_id == account_id:
if account.balance == 0:
@@ -348,22 +372,26 @@ def delete_account(account_id:str): # Deletes an account from the database
return format_response(False, "Account not found."), 404
@admin_required
def get_all_clients(): # Returns all clients in the database
def get_all_clients():
"""Returns all clients in the database."""
clients = session.query(Client).all()
return format_response(True, "", [client.to_dict() for client in clients]), 200
@admin_required
def get_all_accounts(): # Returns all accounts in the database
def get_all_accounts():
"""Returns all accounts in the database."""
accounts = session.query(Account).all()
return format_response(True, "", [account.to_dict() for account in accounts]), 200
@admin_required
def get_all_transactions(): # Returns all transactions in the database
def get_all_transactions():
"""Returns all transactions in the database."""
transactions = session.query(Transaction).all()
return format_response(True, "", [transaction.to_dict() for transaction in transactions]), 200
@admin_required
def apply_interest(account_id:int, interest_rate:float):
"""Applies interest to an account based on the interest rate. If the account is not found, returns an error message."""
for account in session.query(Account).filter(Account.account_id == account_id):
if account.account_id == account_id:
interest = account.balance * interest_rate
@@ -374,6 +402,7 @@ def apply_interest(account_id:int, interest_rate:float):
@admin_required
def apply_fee(account_id:int, fee:float):
"""Applies a fee to an account based on the fee amount. If the account is not found, returns an error message."""
for account in session.query(Account).all():
if account.account_id == account_id:
account.balance -= fee
@@ -383,6 +412,7 @@ def apply_fee(account_id:int, fee:float):
@admin_required
def delete_transaction(transaction_id:int):
"""Deletes a transaction from the database. If the transaction is not found, returns an error message."""
for transaction in session.query(Transaction).all():
if transaction.transaction_id == transaction_id:
session.delete(transaction)
@@ -392,6 +422,7 @@ def delete_transaction(transaction_id:int):
@admin_required
def modify_balance(transaction_id:int, amount:int):
"""Modifies the amount of a transaction in the database. If the transaction is not found, returns an error message."""
for transaction in session.query(Transaction).all():
if transaction.transaction_id == transaction_id:
transaction.amount = amount
@@ -401,6 +432,7 @@ def modify_balance(transaction_id:int, amount:int):
@admin_required
def test_account_balances():
"""Checks all account balances in the database and returns a list of discrepancies."""
all_transactions = session.query(Transaction).all()# Get all transactions from the database
calculated_balances = {} # Initialize a dictionary to store the calculated balance for each account
for transaction in all_transactions: # Go through each transaction
@@ -418,7 +450,8 @@ def test_account_balances():
return format_response(True, "", discrepancies), 200 # Return the list of discrepancies
@admin_required
def add_client(name:str, birthdate:str, address:str, phone_number:str, email:str, password:str, **kwargs): # Adds a new client to the database
def add_client(name:str, birthdate:str, address:str, phone_number:str, email:str, password:str, **kwargs):
"""Adds a new client to the database."""
client_id = generate_uuid_short()
notes = kwargs.get("notes", None)
new_client = Client(client_id, name, birthdate, timestamp(), address, phone_number, email, hash_password(password), notes, 1, 0, None)
@@ -427,6 +460,7 @@ def add_client(name:str, birthdate:str, address:str, phone_number:str, email:str
return format_response(True, f"New client has been added: client_id: {client_id}"), 200
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
@@ -439,6 +473,7 @@ def initialise_database(password:str, email:str):
@admin_required
def promote_to_admin(client_id:str):
"""Promotes a client to administrator status. If the client is not found, returns an error message."""
for client in session.query(Client).all():
if client.client_id == client_id:
client.administrator = 1
@@ -448,6 +483,7 @@ def promote_to_admin(client_id:str):
@admin_required
def demote_from_admin(client_id:str):
"""Demotes a client from administrator status. If the client is not found, returns an error message."""
for client in session.query(Client).all():
if client.client_id == client_id:
client.administrator = 0