Working on CLI Implementation
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ bank.db
|
|||||||
log.txt
|
log.txt
|
||||||
bank.ini
|
bank.ini
|
||||||
app.ini
|
app.ini
|
||||||
|
cli.ini
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
43
cli/class_account.py
Normal file
43
cli/class_account.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Lucas Mathews - Fontys Student ID: 5023572
|
||||||
|
# Banking System Account Class
|
||||||
|
|
||||||
|
from sqlalchemy import ForeignKey, Column, String, Integer, Boolean
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from class_base import Base
|
||||||
|
|
||||||
|
class Account(Base):
|
||||||
|
__tablename__ = 'accounts'
|
||||||
|
account_id = Column("account_id", String, primary_key=True)
|
||||||
|
client_id = Column(String, ForeignKey('clients.client_id'))
|
||||||
|
description = Column("description", String)
|
||||||
|
open_timestamp = Column("open_timestamp", String)
|
||||||
|
account_type = Column("account_type", String)
|
||||||
|
balance = Column("balance", Integer)
|
||||||
|
enabled = Column("enabled", Boolean)
|
||||||
|
notes = Column("notes", String)
|
||||||
|
transactions = relationship("Transaction", foreign_keys='Transaction.account_id', backref="account")
|
||||||
|
|
||||||
|
def __init__(self, account_id, client_id, description, open_timestamp, account_type, balance, enabled, notes, transactions):
|
||||||
|
"""Initialises the account object."""
|
||||||
|
self.account_id = account_id
|
||||||
|
self.client_id = client_id
|
||||||
|
self.description = description
|
||||||
|
self.open_timestamp = open_timestamp
|
||||||
|
self.account_type = account_type
|
||||||
|
self.balance = balance
|
||||||
|
self.enabled = enabled
|
||||||
|
self.notes = notes
|
||||||
|
self.transactions = transactions if transactions is not None else []
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""Returns the account as a dictionary."""
|
||||||
|
return {
|
||||||
|
"account_id": self.account_id,
|
||||||
|
"client_id": self.client_id,
|
||||||
|
"description": self.description,
|
||||||
|
"open_timestamp": self.open_timestamp,
|
||||||
|
"account_type": self.account_type,
|
||||||
|
"balance": self.balance,
|
||||||
|
"notes": self.notes
|
||||||
|
}
|
||||||
6
cli/class_base.py
Normal file
6
cli/class_base.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Lucas Mathews - Fontys Student ID: 5023572
|
||||||
|
# Banking System Base Class
|
||||||
|
|
||||||
|
from sqlalchemy.orm import declarative_base
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
50
cli/class_client.py
Normal file
50
cli/class_client.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Lucas Mathews - Fontys Student ID: 5023572
|
||||||
|
# Banking System Client Class
|
||||||
|
|
||||||
|
from sqlalchemy import Column, String, Boolean
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from class_base import Base
|
||||||
|
|
||||||
|
class Client(Base):
|
||||||
|
__tablename__ = 'clients'
|
||||||
|
client_id = Column("client_id", String, primary_key=True)
|
||||||
|
name = Column("name", String)
|
||||||
|
birthdate = Column("birthdate", String)
|
||||||
|
opening_timestamp = Column("opening_timestamp", String)
|
||||||
|
address = Column("address", String)
|
||||||
|
phone_number = Column("phone_number", String)
|
||||||
|
email = Column("email", String)
|
||||||
|
hash = Column("hash", String)
|
||||||
|
notes = Column("notes", String)
|
||||||
|
enabled = Column("enabled", Boolean)
|
||||||
|
administrator = Column("administrator", Boolean)
|
||||||
|
accounts = relationship("Account", backref="client")
|
||||||
|
|
||||||
|
def __init__(self, client_id, name, birthdate, opening_timestamp, address, phone_number, email, hash, notes, enabled, administrator, accounts):
|
||||||
|
"""Initialises the client object."""
|
||||||
|
self.client_id = client_id
|
||||||
|
self.name = name
|
||||||
|
self.birthdate = birthdate
|
||||||
|
self.opening_timestamp = opening_timestamp
|
||||||
|
self.address = address
|
||||||
|
self.phone_number = phone_number
|
||||||
|
self.email = email
|
||||||
|
self.hash = hash
|
||||||
|
self.notes = notes
|
||||||
|
self.enabled = enabled
|
||||||
|
self.administrator = administrator
|
||||||
|
self.accounts = accounts if accounts is not None else []
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""Returns the client as a dictionary."""
|
||||||
|
return {
|
||||||
|
"client_id": self.client_id,
|
||||||
|
"name": self.name,
|
||||||
|
"birthdate": self.birthdate,
|
||||||
|
"opening_timestamp": self.opening_timestamp,
|
||||||
|
"address": self.address,
|
||||||
|
"phone_number": self.phone_number,
|
||||||
|
"email": self.email,
|
||||||
|
}
|
||||||
|
|
||||||
38
cli/class_transaction.py
Normal file
38
cli/class_transaction.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Lucas Mathews - Fontys Student ID: 5023572
|
||||||
|
# Banking System Transaction Class
|
||||||
|
|
||||||
|
from sqlalchemy import Column, String, Integer, ForeignKey
|
||||||
|
|
||||||
|
from class_base import Base
|
||||||
|
|
||||||
|
class Transaction(Base):
|
||||||
|
__tablename__ = 'transactions'
|
||||||
|
transaction_id = Column("transaction_id", String, primary_key=True)
|
||||||
|
transaction_type = Column("transaction_type", String)
|
||||||
|
amount = Column("amount", Integer)
|
||||||
|
timestamp = Column("timestamp", String)
|
||||||
|
description = Column("description", String)
|
||||||
|
account_id = Column(String, ForeignKey('accounts.account_id'))
|
||||||
|
recipient_account_id = Column(String, ForeignKey('accounts.account_id'))
|
||||||
|
|
||||||
|
def __init__(self, transaction_id, transaction_type, amount, timestamp, description, account_id, recipient_account_id = None):
|
||||||
|
"""Initialises the Transaction object."""
|
||||||
|
self.transaction_id = transaction_id
|
||||||
|
self.transaction_type = transaction_type
|
||||||
|
self.amount = amount
|
||||||
|
self.timestamp = timestamp
|
||||||
|
self.description = description
|
||||||
|
self.account_id = account_id
|
||||||
|
self.recipient_account_id = recipient_account_id
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""Converts the Transaction object to a dictionary."""
|
||||||
|
return {
|
||||||
|
"transaction_id": self.transaction_id,
|
||||||
|
"transaction_type": self.transaction_type,
|
||||||
|
"amount": self.amount,
|
||||||
|
"timestamp": self.timestamp,
|
||||||
|
"description": self.description,
|
||||||
|
"account_id": self.account_id,
|
||||||
|
"recipient_account_id": self.recipient_account_id
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[server]
|
[server]
|
||||||
ip = 0.0.0.0
|
ip = 0.0.0.0
|
||||||
port = 81
|
port = 8066
|
||||||
url = http://127.0.0.1:81
|
url = http://127.0.0.1:8066
|
||||||
|
|
||||||
[client]
|
[client]
|
||||||
default_id =
|
default_id = a9fa4899
|
||||||
default_password =
|
default_password = Happymeal1
|
||||||
10
cli/cli.py
10
cli/cli.py
@@ -22,10 +22,16 @@ def main():
|
|||||||
print("Username and password are required for login.")
|
print("Username and password are required for login.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
response = login(args.username, args.password)
|
response = login(args.username, args.password)
|
||||||
print(f"{response.status_code}: {response.content}")
|
if response['success']:
|
||||||
|
print(f"Login successful: {response['message']}")
|
||||||
|
else:
|
||||||
|
print(f"Login failed: {response['message']}")
|
||||||
elif args.command == 'logout':
|
elif args.command == 'logout':
|
||||||
response = logout()
|
response = logout()
|
||||||
print(f"{response.status_code}: {response.content}")
|
if response['success']:
|
||||||
|
print(f"Logout successful: {response['message']}")
|
||||||
|
else:
|
||||||
|
print(f"Check Credentials: {response['message']}")
|
||||||
else:
|
else:
|
||||||
print("Invalid command. Use 'login' or 'logout'.")
|
print("Invalid command. Use 'login' or 'logout'.")
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
CONFIG = configparser.ConfigParser()
|
CONFIG = configparser.ConfigParser()
|
||||||
CONFIG.read("cli/cli.ini")
|
CONFIG.read("cli.ini")
|
||||||
|
|||||||
@@ -1,14 +1,50 @@
|
|||||||
# api_client.py
|
# Lucas Mathews - Fontys Student ID: 5023572
|
||||||
|
# Banking System CLI Utility Connection File
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from config import CONFIG
|
from config import CONFIG
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
|
||||||
def login(username, password):
|
##############
|
||||||
url = f"{CONFIG['server']['url']}/login"
|
### System ###
|
||||||
payload = {'username': username, 'password': password}
|
##############
|
||||||
response = requests.get(url, params=payload)
|
|
||||||
return response
|
def format_balance(balance):
|
||||||
|
"""Formats the balance as a currency string with comma separators."""
|
||||||
|
return f"€{balance:,.2f}"
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
#####################
|
||||||
|
### API Functions ###
|
||||||
|
#####################
|
||||||
|
|
||||||
|
def login(client_id, password):
|
||||||
|
"""Authenticates a client with the given client_id and client_hash."""
|
||||||
|
try:
|
||||||
|
client_hash = hash_password(password)
|
||||||
|
response = requests.post(CONFIG["server"]["url"] + "/Client/Login", json={'client_id': client_id, 'client_hash': client_hash})
|
||||||
|
response.raise_for_status()
|
||||||
|
response_content = json.loads(response.content) # Parse the JSON response
|
||||||
|
if response.status_code == 200 and response_content.get('success'):
|
||||||
|
return {'success': True, 'message': response_content.get('message')}
|
||||||
|
else:
|
||||||
|
return {'success': False, 'message': response_content.get('message')}
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return {'success': False, 'message': str(e)}
|
||||||
|
|
||||||
def logout():
|
def logout():
|
||||||
url = f"{CONFIG['server']['url']}/logout"
|
url = f"{CONFIG['server']['url']}/logout"
|
||||||
response = requests.get(url)
|
try:
|
||||||
return response
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
response_content = json.loads(response.content) # Parse the JSON response
|
||||||
|
if response.status_code == 200 and response_content.get('success'):
|
||||||
|
return {'success': True, 'message': response_content.get('message')}
|
||||||
|
else:
|
||||||
|
return {'success': False, 'message': response_content.get('message')}
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return {'success': False, 'message': str(e)}
|
||||||
|
|||||||
8
cli/template_cli.ini
Normal file
8
cli/template_cli.ini
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[server]
|
||||||
|
ip = 0.0.0.0
|
||||||
|
port = 8066
|
||||||
|
url = http://127.0.0.1:8066
|
||||||
|
|
||||||
|
[client]
|
||||||
|
default_id =
|
||||||
|
default_password =
|
||||||
128
cli/test_database_generator.py
Normal file
128
cli/test_database_generator.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Lucas Mathews - Fontys Student ID: 5023572
|
||||||
|
# Banking System Test Database Generator
|
||||||
|
|
||||||
|
# This program generates a test database for the banking system. The database contains 50 clients, each with 2 accounts. Each account has 40 transactions.
|
||||||
|
# The first client is an administrator. The password for the administrator account is "Happymeal1". The program uses the Faker library to generate fake
|
||||||
|
# data for the clients, accounts, and transactions. The random library is used to generate random data for the accounts and transactions. The program
|
||||||
|
# creates a new SQLite database called test_database.db and writes the test data to the database. The client ID of the administrator account and the
|
||||||
|
# password for the administrator account.
|
||||||
|
|
||||||
|
ADMIN_EMAIL = "lmath56@hotmail.com" # Email address of the administrator account
|
||||||
|
|
||||||
|
|
||||||
|
from faker import Faker
|
||||||
|
import class_account
|
||||||
|
import class_client
|
||||||
|
import class_transaction
|
||||||
|
from connection import login, add_client, add_account, add_transaction, logout
|
||||||
|
import argparse
|
||||||
|
import random
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
def generate_hash(): # Creates a hash for a password
|
||||||
|
seed = str(random.random()).encode('utf-8')
|
||||||
|
return hashlib.sha512(seed).hexdigest()
|
||||||
|
|
||||||
|
def generate_uuid(): # Generates a unique identifier for transactions
|
||||||
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
def generate_uuid_short(): # Generates a short uuid for accounts and clients
|
||||||
|
return str(uuid.uuid4())[:8]
|
||||||
|
|
||||||
|
def timestamp(): # Returns the current timestamp
|
||||||
|
return (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
|
||||||
|
fake = Faker() # Create a Faker instance
|
||||||
|
|
||||||
|
all_account_ids = [] # List to store all account IDs
|
||||||
|
|
||||||
|
# Set up argument parsing
|
||||||
|
parser = argparse.ArgumentParser(description="Generate test database for the banking system.")
|
||||||
|
parser.add_argument('-u', '--username', required=True, help="Username for admin login")
|
||||||
|
parser.add_argument('-p', '--password', required=True, help="Password for admin login")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Log in as the admin using provided username and password
|
||||||
|
client_id = args.username
|
||||||
|
client_hash = hashlib.sha512(args.password.encode()).hexdigest()
|
||||||
|
client = login(client_id, client_hash)
|
||||||
|
|
||||||
|
if client is not None: # Check if login was successful
|
||||||
|
print("Admin logged in successfully")
|
||||||
|
|
||||||
|
for i in range(50): # Generate 50 clients
|
||||||
|
is_administrator = 1 if i == 0 else 0 # Set the first client as an administrator
|
||||||
|
# Set the password hash for the first account so that the password is "Happymeal1"
|
||||||
|
password = "Happymeal1" if i == 0 else generate_hash()
|
||||||
|
client_id = generate_uuid_short()
|
||||||
|
client_name = "ADMIN" if i == 0 else fake.name()
|
||||||
|
birthdate = "ADMIN" if i == 0 else fake.date_of_birth(minimum_age=18, maximum_age=90)
|
||||||
|
opening_timestamp = timestamp() if i == 0 else fake.date_this_century()
|
||||||
|
address = "ADMIN" if i == 0 else fake.address()
|
||||||
|
phone_number = "ADMIN" if i == 0 else fake.phone_number()
|
||||||
|
email = ADMIN_EMAIL if i == 0 else fake.email()
|
||||||
|
notes = fake.text(max_nb_chars=50)
|
||||||
|
|
||||||
|
# Add client using add_client function
|
||||||
|
client_response = add_client(
|
||||||
|
name=client_name,
|
||||||
|
birthdate=birthdate,
|
||||||
|
address=address,
|
||||||
|
phone_number=phone_number,
|
||||||
|
email=email,
|
||||||
|
password=password,
|
||||||
|
notes=notes
|
||||||
|
)
|
||||||
|
print(client_response[1]) # Print the response message
|
||||||
|
|
||||||
|
for j in range(2): # Each client has 2 accounts
|
||||||
|
account_id = generate_uuid_short()
|
||||||
|
balance = 1000 # Initialize balance to 1000
|
||||||
|
account_type = random.choice(['Spending', 'Savings'])
|
||||||
|
account_notes = fake.text(max_nb_chars=50)
|
||||||
|
|
||||||
|
# Add account using add_account function
|
||||||
|
account_response = add_account(
|
||||||
|
client_id=client_id,
|
||||||
|
description=fake.text(max_nb_chars=200),
|
||||||
|
account_type=account_type,
|
||||||
|
notes=account_notes
|
||||||
|
)
|
||||||
|
print(account_response[1]) # Print the response message
|
||||||
|
|
||||||
|
for k in range(40): # Each account has 40 transactions
|
||||||
|
if not all_account_ids: # Skip creating a transaction if there are no accounts yet
|
||||||
|
continue
|
||||||
|
|
||||||
|
transaction_type = random.choice(['Deposit', 'Withdrawal'])
|
||||||
|
amount = random.randint(1, 200)
|
||||||
|
|
||||||
|
if transaction_type == 'Withdrawal' and balance - amount < 0: # Skip withdrawal if it would make balance negative
|
||||||
|
continue
|
||||||
|
|
||||||
|
if transaction_type == 'Deposit': # Update balance based on transaction type
|
||||||
|
balance += amount
|
||||||
|
elif transaction_type == 'Withdrawal':
|
||||||
|
balance -= amount
|
||||||
|
|
||||||
|
transaction_description = fake.text(max_nb_chars=50)
|
||||||
|
recipient_account_id = random.choice(all_account_ids)
|
||||||
|
|
||||||
|
# Add transaction using add_transaction function
|
||||||
|
transaction_response = add_transaction(
|
||||||
|
amount=amount,
|
||||||
|
account_id=account_id,
|
||||||
|
recipient_account_id=recipient_account_id,
|
||||||
|
otp_code=123456, # Replace with actual OTP verification code
|
||||||
|
description=transaction_description
|
||||||
|
)
|
||||||
|
print(transaction_response[1]) # Print the response message
|
||||||
|
|
||||||
|
all_account_ids.append(account_id)
|
||||||
|
logout() # Log out of the admin account
|
||||||
|
print(f"The client_id of the administrator account of this test database is: {all_account_ids[0]}. The password is: Happymeal1")
|
||||||
|
else:
|
||||||
|
print("Admin login failed.")
|
||||||
@@ -5,13 +5,13 @@ from class_client import Client
|
|||||||
from class_account import Account
|
from class_account import Account
|
||||||
from class_transaction import Transaction
|
from class_transaction import Transaction
|
||||||
from emailer import EmailSendingError # Import the EmailSendingError class to handle email sending errors
|
from emailer import EmailSendingError # Import the EmailSendingError class to handle email sending errors
|
||||||
from flask import jsonify, session as flask_session # Imports the Flask modules
|
|
||||||
from functools import wraps # For decorators / user login
|
from functools import wraps # For decorators / user login
|
||||||
from database import * # Importing the database connection
|
from database import * # Importing the database connection
|
||||||
from emailer import send_email # Importing the emailer function
|
from emailer import send_email # Importing the emailer function
|
||||||
from logger import event_logger # Importing the event_logger function
|
from logger import event_logger # Importing the event_logger function
|
||||||
from flask import session as flask_session
|
from flask import session as flask_session
|
||||||
from flask import request
|
from flask import request
|
||||||
|
from flask import jsonify # Imports the Flask modules
|
||||||
from database import session
|
from database import session
|
||||||
import hashlib # For password hashing
|
import hashlib # For password hashing
|
||||||
import datetime # For timestamps
|
import datetime # For timestamps
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def clean_otp():
|
|||||||
event_logger(f"Removed {removed_otps} expired OTPs.")
|
event_logger(f"Removed {removed_otps} expired OTPs.")
|
||||||
event_logger("Finished cleaning OTPs.")
|
event_logger("Finished cleaning OTPs.")
|
||||||
|
|
||||||
schedule.every(300).seconds.do(clean_otp)
|
schedule.every(60).seconds.do(clean_otp)
|
||||||
|
|
||||||
thread = threading.Thread(target=run_schedule)
|
thread = threading.Thread(target=run_schedule)
|
||||||
thread.daemon = True # Set the thread as a daemon thread
|
thread.daemon = True # Set the thread as a daemon thread
|
||||||
|
|||||||
Reference in New Issue
Block a user