diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..db4f3a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System DockerFile + +FROM python:3.12.3 + +LABEL maintainer="522499@student.fontys.nl" + +WORKDIR /bank + +COPY / /bank/ + +EXPOSE 81 + +RUN pip install --no-cache-dir --upgrade -r /bank/requirements.txt + +ENTRYPOINT [ "python", "./api.py", "--host", "0.0.0.0", "--port", "81"] diff --git a/Flask App/ARG/agent.py b/Flask App/ARG/agent.py new file mode 100644 index 0000000..022c58e --- /dev/null +++ b/Flask App/ARG/agent.py @@ -0,0 +1,8 @@ +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('--x', type=int, required=True) +parser.add_argument('--y', type=int, required=True) +args = parser.parse_args() +product = args.x * args.y +print(f"{args.x} * {args.y} = {product}") \ No newline at end of file diff --git a/Flask App/app.py b/Flask App/app.py new file mode 100644 index 0000000..2a0670b --- /dev/null +++ b/Flask App/app.py @@ -0,0 +1,19 @@ +from flask import Flask, render_template, request +import requests + +SERVER_URL = "http://127.0.0.1:8000" + +app = Flask(__name__) + +@app.route("/", methods=["GET"]) +def loginGET(): + return render_template("login.html") + +@app.route("/", methods=["POST"]) +def loginPOST(): + username = request.form["username"] + password = request.form["password"] + response = requests.get( f"{SERVER_URL}/login?username={username}&password={password}") + return render_template("succes.html", data = response.content) + +app.run() \ No newline at end of file diff --git a/Flask App/main.py b/Flask App/main.py new file mode 100644 index 0000000..e69de29 diff --git a/Flask App/templates/login.html b/Flask App/templates/login.html new file mode 100644 index 0000000..1617531 --- /dev/null +++ b/Flask App/templates/login.html @@ -0,0 +1,17 @@ + + +
+ + +{{data}}
+ + \ No newline at end of file diff --git a/Old/api.yml b/Old/api.yml new file mode 100644 index 0000000..5939103 --- /dev/null +++ b/Old/api.yml @@ -0,0 +1,686 @@ +openapi: 3.0.3 +info: + title: Banking System - OpenAPI 3.0 + description: |- + This is the banking system API for the programming project. + contact: + email: 522499@student.fontys.nl + version: 1.0.11 +servers: + - url: / +tags: + - name: accounts + description: Everything about your Accounts + - name: user + description: Operations about user +paths: + /Accounts: + put: + tags: + - accounts + summary: Update an existing account + description: Update an existing account by Id + operationId: updateAccount + requestBody: + description: Update an existing bank account + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + application/xml: + schema: + $ref: '#/components/schemas/Account' + text/html: + schema: + $ref: '#/components/schemas/Account' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + application/xml: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '422': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - accounts + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + application/xml: + schema: + $ref: '#/components/schemas/Account' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Account' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + application/xml: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Invalid input + '422': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - accounts + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Account' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Account' + '400': + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - accounts + summary: Finds Pets by tags + description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Account' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Account' + '400': + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - accounts + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + application/xml: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - accounts + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + '400': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - accounts + summary: Deletes a pet + description: delete a pet + operationId: deletePet + parameters: + - name: api_key + in: header + description: '' + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - accounts + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + /user/{username}: + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Update user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Customer: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + $ref: '#/components/schemas/Address' + xml: + name: customer + Address: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: '94301' + xml: + name: address + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: '12345' + phone: + type: string + example: '12345' + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Account: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + application/xml: + schema: + $ref: '#/components/schemas/Account' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header \ No newline at end of file diff --git a/Old/class.py b/Old/class.py new file mode 100644 index 0000000..66fd34f --- /dev/null +++ b/Old/class.py @@ -0,0 +1,59 @@ + + + +class Transaction: + def __init__(self, trans_id, from_id, to_id, amount, time, date, description, t_type): + self.trans_id = trans_id + self.from_id = from_id + self.to_id = to_id + self.amount = amount + self.time = time + self.date = date + self.description = description + self.t_type = t_type + + +class Account: + def __init__(self, account_id, name, balance, created_t, created_d, last_modified, closed, closure_t, closure_d, + notes, transactions=None): + if transactions is None: + transactions = [] + self.account_id = account_id + self.name = name + self.balance = balance + self.created_t = created_t + self.created_d = created_d + self.last_modified = last_modified + self.closed = closed + self.closure_t = closure_t + self.closure_d = closure_d + self.notes = notes + self.transactions = transactions + + +class Customer: + def __init__(self, customer_id, f_name, l_name, phone, email, birthday, address, signup_d, signup_t, notes, + accounts=None): + if accounts is None: + accounts = [] + self.customer_id = customer_id + self.f_name = f_name + self.l_name = l_name + self.phone = phone + self.email = email + self.birthday = birthday + self.address = address + self.signup_d = signup_d + self.signup_t = signup_t + self.notes = notes + self.accounts = accounts + +#calculate the balance of an account +def calc_balance(account): + balance = 0 + for transaction in account.transactions: + if transaction.from_id == account.account_id: + balance -= transaction.amount + elif transaction.to_id == account.account_id: + balance += transaction.amount + return balance \ No newline at end of file diff --git a/Old/manager_account.py b/Old/manager_account.py new file mode 100644 index 0000000..3ce6dc4 --- /dev/null +++ b/Old/manager_account.py @@ -0,0 +1,4 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Manager for Account Class - Version 1 + + diff --git a/Old/manager_transaction.py b/Old/manager_transaction.py new file mode 100644 index 0000000..37f5431 --- /dev/null +++ b/Old/manager_transaction.py @@ -0,0 +1,24 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Manager for Transaction Class - Version 1 + + +def add_transaction(transaction_id, transaction_type, amount, timestamp, description, account_number, recipient_account_number): + from api import session, Transaction + new_transaction = Transaction(transaction_id, transaction_type, amount, timestamp, description, account_number, recipient_account_number) + session.add(new_transaction) + session.commit() + return new_transaction + +def delete_transaction(transaction_id:int): + DELETE_TRANSACTION = "DELETE FROM transaction WHERE transaction_id=?" + from api import session, Transaction + for transaction in session.query(Transaction).all(): + if transaction.transaction_id == transaction_id: + input(f"Are you sure you would like permanenty delete transaction ID: {transaction_id}? WARNING: This action can not be reversed. (Y/N) ") + if input == "Y"or input == "y": + session.execute(DELETE_TRANSACTION, (transaction_id)) + print(f"Transaction ID: {transaction_id} has been removed.") + else: + return f"Transaction ID: {transaction_id} has NOT been removed." + return + return f"Transaction ID: {transaction_id} is not found." \ No newline at end of file diff --git a/Old/server.py b/Old/server.py new file mode 100644 index 0000000..2c2aa68 --- /dev/null +++ b/Old/server.py @@ -0,0 +1,196 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Classes - Version 2 + +############### +### Modules ### +############### + +import sqlite3 +import os.path +import datetime +import connexion +from config import CONFIG + +################# +### Functions ### +################# + + + + +############### +### Classes ### +############### + +class Manager: + def __init__(): + pass + + + +class Client: + def __init__(self, client_id, name, opening_timestamp, birthdate, address, phone_number, email:str, password, accounts, notes): + 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.password = password + self.accounts = accounts + self.notes = notes + + #If you call a print function on the object, it will return the following string (does not include password for security reasons) + def __str__(self): + return f"Client ID: {self.client_id}, Name: {self.name}, Birthdate: {self.birthdate}, Address: {self.address}, Phone Number: {self.phone_number}, Email: {self.email}" + + #This function will return the account list + def get_accounts(self)->str: + return f"Accounts: {self.accounts}" + + #Change details (name, birthdate, address, phone number, email, password) + #Change password + +class Account: + def __init__(self, account_id, description, open_timestamp, account_type, balance, enabled, notes, transactions): + self.account_id = account_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 you call a print function on the object, it will return the following string + def __str__(self): + return f"Account ID: {self.account_id}, Description: {self.description}, Open Timestamp: {self.open_timestamp}, Account Type: {self.account_type}, Balance: {self.balance}, Enabled: {self.enabled}, Notes: {self.notes}, Transactions: {self.transactions}" + + # This function will return the transaction history of an account + def transaction_history(self, account_id:int): + return self.transactions + + #This function will remove the account + def remove_account(self, account_id:int): + REMOVE_ACCOUNT = "DELETE FROM account WHERE account_id=?" + for account in self.accounts: + if account.balance != 0: #If the account has a balance, it can not be removed + return f"Account ID: {account_id} has a balance of {account.balance} and can not be removed." + if account.account_id == account_id: #Check if account exists + input(f"Are you sure you would like permanenty delete account ID: {account_id}? WARNING: This action can not be reversed. (Y/N) ") + if input == "Y"or input == "y": #If the user inputs Y or y, the account will be removed + db_conn = get_db_connection() + cursor = db_conn.cursor() + cursor.execute(REMOVE_ACCOUNT, (account_id, ) ) + db_conn.commit() + print(f"Account ID: {account_id} has been removed.") + else: + return f"Account ID: {account_id} has NOT been removed." + return + return f"Account ID: {account_id} is not found." + + #This function will return the account balance + def account_balance(account_id:int): + GET_ACCOUNT = "SELECT balance FROM account WHERE account_id = ?" + + db_conn = get_db_connection() + cursor = db_conn.cursor() + cursor.execute(GET_ACCOUNT, (account_id) ) + resultset = cursor.fetchall() + db_conn.close() + + if len(resultset) < 1: + return "Not found", 404 + elif len(resultset) > 2: + return "Too many results found.", 500 + +class Transaction: + def __init__(self, transaction_id, transaction_type, amount, timestamp, description, account_number, recipient_account_number = None): + self.transaction_id = transaction_id + self.transaction_type = transaction_type + self.amount = amount + self.timestamp = timestamp + self.description = description + self.account_number = account_number + self.recipient_account_number = recipient_account_number + + def __str__(self): + return f"Transaction ID: {self.transaction_id}, Transaction Type: {self.transaction_type}, Amount: {self.amount}, Timestamp: {self.timestamp}, Description: {self.description} From Account Number: {self.account_number}, Recipient Account Number: {self.recipient_account_number}" + +################ +### Database ### +################ + +def Database(): + CLIENT_TABLE_CREATION_QUERY = """ + CREATE TABLE IF NOT EXISTS client ( + client_id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + birthdate DATE NOT NULL, + opening_timestamp TIMESTAMP NOT NULL, + address TEXT NOT NULL, + phone_number TEXT NOT NULL, + email TEXT NOT NULL, + password TEXT NOT NULL, + notes TEXT NOT NULL + ) + """ + ACCOUNT_TABLE_CREATION_QUERY = """ + CREATE TABLE IF NOT EXISTS account ( + account_id INTEGER PRIMARY KEY AUTOINCREMENT, + description TEXT NOT NULL, + open_timestamp TIMESTAMP NOT NULL, + account_type TEXT NOT NULL, + balance REAL NOT NULL, + enabled BOOLEAN NOT NULL, + notes TEXT NOT NULL, + client_id INTEGER NOT NULL, + FOREIGN KEY (client_id) REFERENCES client(client_id) + ) + """ + TRANSACT_TABLE_CREATION_QUERY = """ + CREATE TABLE IF NOT EXISTS transact ( + transaction_id INTEGER PRIMARY KEY AUTOINCREMENT, + transaction_type TEXT NOT NULL, + amount REAL NOT NULL, + timestamp TIMESTAMP NOT NULL, + description TEXT NOT NULL, + account_id INTEGER NOT NULL, + recipient_account_id INTEGER, + FOREIGN KEY (account_id) REFERENCES account(account_id) + ) + """ + # Check if the database exists + if os.path.exists('bank.db'): + print("Database already exists.") + else: + print("Database does not exist. Creating database.") + + # Create the database and the tables if they do not exist, or connect to the database if it does exist + db_connection = sqlite3.connect('bank.db') + db_cursor = db_connection.cursor() + db_cursor.execute(CLIENT_TABLE_CREATION_QUERY) + db_cursor.execute(ACCOUNT_TABLE_CREATION_QUERY) + db_cursor.execute(TRANSACT_TABLE_CREATION_QUERY) + db_connection.commit() + + +################# +### Connexion ### +################# + +def API(): + app = connexion.App(__name__) + app.add_api('api.yml') + app.run(host=CONFIG["server"]["listen_address"], port=CONFIG["server"]["port"], debug=CONFIG["server"]["debug"]) + + + +################ +### Run Code ### +################ + +if __name__ == "__main__": + Database() + API() \ No newline at end of file diff --git a/__pycache__/class_account.cpython-312.pyc b/__pycache__/class_account.cpython-312.pyc new file mode 100644 index 0000000..83ba42b Binary files /dev/null and b/__pycache__/class_account.cpython-312.pyc differ diff --git a/__pycache__/class_base.cpython-312.pyc b/__pycache__/class_base.cpython-312.pyc new file mode 100644 index 0000000..208f23b Binary files /dev/null and b/__pycache__/class_base.cpython-312.pyc differ diff --git a/__pycache__/class_client.cpython-312.pyc b/__pycache__/class_client.cpython-312.pyc new file mode 100644 index 0000000..2f95a70 Binary files /dev/null and b/__pycache__/class_client.cpython-312.pyc differ diff --git a/__pycache__/class_transaction.cpython-312.pyc b/__pycache__/class_transaction.cpython-312.pyc new file mode 100644 index 0000000..0a1c27c Binary files /dev/null and b/__pycache__/class_transaction.cpython-312.pyc differ diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..1cce7e6 Binary files /dev/null and b/__pycache__/config.cpython-312.pyc differ diff --git a/__pycache__/database.cpython-312.pyc b/__pycache__/database.cpython-312.pyc new file mode 100644 index 0000000..dff1d5c Binary files /dev/null and b/__pycache__/database.cpython-312.pyc differ diff --git a/__pycache__/manager.cpython-312.pyc b/__pycache__/manager.cpython-312.pyc new file mode 100644 index 0000000..c5d2327 Binary files /dev/null and b/__pycache__/manager.cpython-312.pyc differ diff --git a/__pycache__/manager_account.cpython-312.pyc b/__pycache__/manager_account.cpython-312.pyc new file mode 100644 index 0000000..c186ef0 Binary files /dev/null and b/__pycache__/manager_account.cpython-312.pyc differ diff --git a/__pycache__/manager_client.cpython-312.pyc b/__pycache__/manager_client.cpython-312.pyc new file mode 100644 index 0000000..c3567d0 Binary files /dev/null and b/__pycache__/manager_client.cpython-312.pyc differ diff --git a/__pycache__/manager_transaction.cpython-312.pyc b/__pycache__/manager_transaction.cpython-312.pyc new file mode 100644 index 0000000..31901ef Binary files /dev/null and b/__pycache__/manager_transaction.cpython-312.pyc differ diff --git a/__pycache__/operator_account.cpython-312.pyc b/__pycache__/operator_account.cpython-312.pyc new file mode 100644 index 0000000..cf56d37 Binary files /dev/null and b/__pycache__/operator_account.cpython-312.pyc differ diff --git a/__pycache__/operator_client.cpython-312.pyc b/__pycache__/operator_client.cpython-312.pyc new file mode 100644 index 0000000..c8ed0f1 Binary files /dev/null and b/__pycache__/operator_client.cpython-312.pyc differ diff --git a/__pycache__/operator_transaction.cpython-312.pyc b/__pycache__/operator_transaction.cpython-312.pyc new file mode 100644 index 0000000..3dd14fd Binary files /dev/null and b/__pycache__/operator_transaction.cpython-312.pyc differ diff --git a/__pycache__/password.cpython-312.pyc b/__pycache__/password.cpython-312.pyc new file mode 100644 index 0000000..d82b09c Binary files /dev/null and b/__pycache__/password.cpython-312.pyc differ diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc new file mode 100644 index 0000000..c59d711 Binary files /dev/null and b/__pycache__/server.cpython-312.pyc differ diff --git a/api.py b/api.py new file mode 100644 index 0000000..5c2625f --- /dev/null +++ b/api.py @@ -0,0 +1,26 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System API + +############### +### Modules ### +############### + +import connexion # Imports connexion module +from config import CONFIG # Imports the configuration file +from manager import * # Imports the Manager file that contains the functions for the API + +################# +### Connexion ### +################# + +def API(): + app = connexion.FlaskApp(__name__) + app.add_api(CONFIG["api_file"]["name"]) + app.run(host=CONFIG["server"]["listen_ip"], port=CONFIG["server"]["port"], debug=CONFIG["server"]["debug"]) # Runs the API using the configuration file + +################ +### Run Code ### +################ + +if __name__ == "__main__": + API() \ No newline at end of file diff --git a/api.yml b/api.yml new file mode 100644 index 0000000..19b9cb2 --- /dev/null +++ b/api.yml @@ -0,0 +1,701 @@ +openapi: 3.0.3 +info: + title: Banking API + description: |- + Lucas Mathews - Fontys Student ID: 5023572 + contact: + email: 522499@student.fontys.nl + version: 2.0.0 +servers: + - url: http://127.0.0.1:81 +tags: + - name: client + description: Operations for Client Accounts + - name: account + description: Operations for Bank Accounts + - name: transaction + description: Operations for Transactions + - name: manager + description: Operations for Bank Managers +paths: + /Client/Login: + post: + tags: + - client + summary: Log in to the system + description: Log in to the system + operationId: manager.login_user + requestBody: + description: Credentials for logging in + content: + application/json: + schema: + type: object + properties: + username: + type: string + password: + type: string + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + '400': + description: Invalid username/password supplied + '401': + description: Unauthorized + /Client/Logout: + post: + tags: + - client + summary: Log out from the system + description: Log out from the system + operationId: manager.logout_user + responses: + '200': + description: Successful operation + '401': + description: Unauthorized + /Client/Password: + put: + tags: + - client + summary: Change password + description: Change password + operationId: manager.change_password + parameters: + - name: client_id + in: query + description: ID of client to change password + required: true + schema: + type: integer + format: int32 + - name: password + in: query + description: New password + required: true + schema: + type: string + - name: new_password + in: query + description: New password + required: true + schema: + type: string + responses: + '200': + description: Password changed successfully + '400': + description: Old password incorrect + '404': + description: client_id not found + /Client: + post: + tags: + - client + summary: Add a new client + description: Add a new client to the system + operationId: manager.add_client + parameters: + - name: name + in: query + description: Client Name + required: true + schema: + type: string + - name: birthdate + in: query + description: Client Birthdate (dd-mm-yyyy) + required: true + schema: + type: string + - name: address + in: query + description: Client Address + required: false + schema: + type: string + - name: phone_number + in: query + description: Client Phone Number + required: true + schema: + type: string + - name: email + in: query + description: Client Email Address + required: true + schema: + type: string + - name: password + in: query + description: Client Email Address + required: true + schema: + type: string + - name: notes + in: query + description: Notes about client + required: false + schema: + type: string + responses: + '200': + description: "Client created" + '400': + description: Invalid input + '422': + description: Validation exception + put: + tags: + - client + summary: Update an existing client + description: Update an existing client Id + operationId: manager.update_client + requestBody: + description: Update an existing client's details + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + '400': + description: Invalid Client ID supplied + '404': + description: Client not found + '422': + description: Validation exception + get: + tags: + - client + summary: Get a client by ID + description: Get a client by ID + operationId: manager.get_client + parameters: + - name: client_id + in: query + description: ID of client to return + required: true + schema: + type: integer + format: int32 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + '400': + description: Invalid Client ID supplied + '404': + description: Client not found + delete: + tags: + - client + summary: Delete a client by ID + description: Delete a client by ID + operationId: manager.delete_client + parameters: + - name: client_id + in: query + description: ID of client to delete + required: true + schema: + type: string + format: int32 + responses: + '200': + description: Successful operation + '400': + description: Invalid Client ID supplied + '404': + description: Client not found + /Account: + post: + tags: + - account + summary: Add a new account + description: Add a new account to the system + operationId: manager.add_account + parameters: + - name: description + in: query + description: Account description + required: true + schema: + type: string + - name: account_type + in: query + description: Type of account + required: true + schema: + type: string + - name: notes + in: query + description: Notes about account + required: false + schema: + type: string + responses: + '200': + description: Successful operation + '400': + description: Invalid input + '422': + description: Validation exception + put: + tags: + - account + summary: Update an existing account + description: Update an existing account + operationId: manager.update_account + parameters: + - name: account_id + in: query + description: ID of account to update + required: true + schema: + type: integer + format: int32 + requestBody: + description: Update an existing account + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Invalid Account ID supplied + '404': + description: Account not found + '422': + description: Validation exception + get: + tags: + - account + summary: Get an account by ID + description: Get an account by ID + operationId: manager.get_account + parameters: + - name: account_id + in: query + description: ID of account to return + required: true + schema: + type: integer + format: int32 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Invalid Account ID supplied + '404': + description: Account not found + delete: + tags: + - account + summary: Delete an account by ID + description: Delete an account by ID + operationId: manager.delete_account + parameters: + - name: account_id + in: query + description: ID of account to delete + required: true + schema: + type: string + format: int32 + responses: + '200': + description: Successful operation + '400': + description: Invalid account_id supplied + '404': + description: Account not found + /Transaction: + get: + tags: + - transaction + summary: Get a transaction by ID + description: Get a transaction by ID + operationId: manager.get_transaction + parameters: + - name: transaction_id + in: query + description: ID of transaction to return + required: true + schema: + type: integer + format: int32 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Transaction' + '400': + description: Invalid Transaction ID supplied + '404': + description: Transaction not found + post: + tags: + - transaction + summary: Add a new transaction + description: Add a new transaction to the system + operationId: manager.add_transaction + parameters: + - name: amount + in: query + description: Amount of transaction + required: true + schema: + type: integer + format: int32 + - name: account_from + in: query + description: Account number the money paid from + required: true + schema: + type: string + - name: account_to + in: query + description: Recipient account number + required: true + schema: + type: string + - name: description + in: query + description: Description of transaction + required: false + schema: + type: string + responses: + '200': + description: Successful operation + '400': + description: Invalid input + '422': + description: Validation exception + /Transaction/History: + get: + tags: + - transaction + summary: Get transaction history + description: Get transaction history + operationId: manager.transaction_history + parameters: + - name: account_id + in: query + description: ID of account to return + required: true + schema: + type: integer + format: int32 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Transaction' + '400': + description: Invalid input + '404': + description: No transactions found + /Manager/Interest: + post: + tags: + - manager + summary: Apply interest + description: Apply interest to account + operationId: manager.apply_interest + requestBody: + description: Apply interest to account + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Invalid input + '422': + description: Validation exception + put: + tags: + - manager + summary: Apply fee + description: Apply fee to account + operationId: manager.apply_fee + requestBody: + description: Apply fee to account + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Invalid input + '422': + description: Validation exception + /Manager/Clients: + get: + tags: + - manager + summary: Get all clients + description: Get all clients + operationId: manager.get_all_clients + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + '400': + description: Invalid input + '404': + description: No clients found + /Manager/Accounts: + get: + tags: + - manager + summary: Get all accounts + description: Get all accounts + operationId: manager.get_all_accounts + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Invalid input + '404': + description: No accounts found + /Manager/Transactions: + get: + tags: + - manager + summary: Get all transactions + description: Get all transactions + operationId: manager.get_all_transactions + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Transaction' + '400': + description: Invalid input + '404': + description: No transactions found + put: + tags: + - manager + summary: Update an existing transaction + description: Update an existing transaction + operationId: manager.update_transaction + requestBody: + description: Update an existing transaction + content: + application/json: + schema: + $ref: '#/components/schemas/Transaction' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Transaction' + '400': + description: Invalid Transaction ID supplied + '404': + description: Transaction not found + '422': + description: Validation exception + /Manager/Hash: + get: + tags: + - manager + summary: Hash password + description: Pass a string through the hashing algorithm + operationId: manager.password_hash + parameters: + - name: password + in: query + description: Password to hash + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: string + '400': + description: Invalid input + '401': + description: Unauthorized + /Manager/Timestamp: + get: + tags: + - manager + summary: Get the timestamp + description: Gets the date and time in the appropriate format + operationId: manager.timestamp + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: string + '400': + description: Invalid input + '401': + description: Unauthorized +components: + schemas: + Client: + type: object + properties: + client_id: + type: integer + format: int32 + name: + type: string + birthdate: + type: string + opening_timestamp: + type: string + address: + type: string + phone_number: + type: string + email: + type: string + password: + type: string + notes: + type: string + enabled: + type: boolean + administator: + type: boolean + accounts: + type: array + example: + client_id: 1 + name: "Lucas Mathews" + birthdate: "21-05-1980" + opening_timestamp: "17-04-2022 16:21:12" + address: "Rachelsmolen 1, 5612MA, Eindhoven" + phone_number: "0612345678" + email: "john.d@fontys.nl" + password: "password" + notes: "This is a test client" + enabled: true + administator: false + accounts: [] + Account: + type: object + properties: + account_id: + type: integer + format: int32 + decription: + type: string + opening_timestamp: + type: string + account_type: + type: string + balance: + type: number + enabled: + type: boolean + notes: + type: string + transactons: + type: array + example: + account_id: 1 + description: "Savings Account" + opening_timestamp: "17-04-2022 16:21:12" + account_type: "Rachelsmolen 1, 5612MA, Eindhoven" + balance: 2314.23 + enabled: true + notes: "This is a savings account" + transactions: [] + Transaction: + type: object + properties: + transaction_id: + type: integer + format: int32 + transaction_type: + type: string + amount: + type: integer + format: int32 + timestamp: + type: string + description: + type: string + account_number: + type: string + recipient_account_number: + type: string + example: + transaction_id: 1 + transaction_type: "Deposit" + amount: 100.00 + timestamp: "17-04-2022 16:21:12" + description: "Deposit to Savings Account" + account_number: "NL12ABNA0123456789" + recipient_account_number: "NL12ABNA1234567890" + diff --git a/bank.db b/bank.db new file mode 100644 index 0000000..9d458d4 Binary files /dev/null and b/bank.db differ diff --git a/bank.ini b/bank.ini new file mode 100644 index 0000000..bb1cf16 --- /dev/null +++ b/bank.ini @@ -0,0 +1,20 @@ +[database] +name=bank.db +add_sample_data=True + +[api_file] +name=api.yml + +[server] +listen_ip=127.0.0.1 +port=81 +debug=True + +[frontend] +listen_ip=127.0.0.1 +port=80 +debug=True + +[api] +url=http://127.0.0.1:81/ + diff --git a/class_account.py b/class_account.py new file mode 100644 index 0000000..0347dfc --- /dev/null +++ b/class_account.py @@ -0,0 +1,31 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Account Class + +from sqlalchemy import ForeignKey, Column, String, Integer, Boolean + + +from class_base import Base + +class Account(Base): + __tablename__ = 'accounts' + account_id = Column("account_id", String, primary_key=True) + 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 = ("transactions", String, ForeignKey("transactions.transaction_id")) + + def __init__(self, account_id, description, open_timestamp, account_type, balance, enabled, notes, transactions): + self.account_id = account_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 + + def __repr__(self): + return f"Account ID: {self.account_id}, Description: {self.description}, Open Timestamp: {self.open_timestamp}, Account Type: {self.account_type}, Balance: {self.balance}, Enabled: {self.enabled}, Notes: {self.notes}, Transactions: {self.transactions}" \ No newline at end of file diff --git a/class_base.py b/class_base.py new file mode 100644 index 0000000..0439352 --- /dev/null +++ b/class_base.py @@ -0,0 +1,6 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Base Class + +from sqlalchemy.orm import declarative_base + +Base = declarative_base() \ No newline at end of file diff --git a/class_client.py b/class_client.py new file mode 100644 index 0000000..691fe43 --- /dev/null +++ b/class_client.py @@ -0,0 +1,38 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Client Class + +from sqlalchemy import Column, String, Boolean + +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 = Column("accounts", String) + + def __init__(self, client_id, name, birthdate, opening_timestamp, address, phone_number, email, hash, notes, enabled, administrator, accounts): + 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 + + def __repr__(self): + return f"Client ID: {self.client_id}, Name: {self.name}, Birthdate: {self.birthdate}, Address: {self.address}, Phone Number: {self.phone_number}, Email: {self.email}, Enabled: {self.enabled}, Accounts: {self.accounts}" \ No newline at end of file diff --git a/class_transaction.py b/class_transaction.py new file mode 100644 index 0000000..f548778 --- /dev/null +++ b/class_transaction.py @@ -0,0 +1,28 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Transaction Class + +from sqlalchemy import Column, String, Integer + +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_number = Column("account_number", Integer) + recipient_account_number = Column("recipient_account_number", Integer) + + def __init__(self, transaction_id, transaction_type, amount, timestamp, description, account_number, recipient_account_number = None): + self.transaction_id = transaction_id + self.transaction_type = transaction_type + self.amount = amount + self.timestamp = timestamp + self.description = description + self.account_number = account_number + self.recipient_account_number = recipient_account_number + + def __repr__(self): + return f"Transaction ID: {self.transaction_id}, Transaction Type: {self.transaction_type}, Amount: {self.amount}, Timestamp: {self.timestamp}, Description: {self.description} From Account Number: {self.account_number}, Recipient Account Number: {self.recipient_account_number}" \ No newline at end of file diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..2c3411b --- /dev/null +++ b/cli.py @@ -0,0 +1,19 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System CLI Utility + +import requests + +SERVER_URL = "http://127.0.0.1:5000" + +def main(): + username = "john" + password = "doe" + print(f"Login with {username} and {password}:") + response = requests.get( f"{SERVER_URL}/login?username={username}&password={password}") + print(f"{response}, {response.content}") + + print(f"Logout:") + response = requests.get( f"{SERVER_URL}/logout") + print(f"{response}, {response.content}") + print(f"Closing") +main() \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..bcdb1a6 --- /dev/null +++ b/config.py @@ -0,0 +1,7 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Config Parser + +import configparser + +CONFIG = configparser.ConfigParser() +CONFIG.read("bank.ini") diff --git a/database.py b/database.py new file mode 100644 index 0000000..2661569 --- /dev/null +++ b/database.py @@ -0,0 +1,58 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Manager File + +import os.path + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +#Import Config +from config import CONFIG + +# Check if the database exists +if os.path.exists(CONFIG["database"]["name"]): + print(f"Database {CONFIG["database"]["name"]} already exists.") +else: + print(f"Database {CONFIG["database"]["name"]} does not exist. Creating it now.") + +# Sets the database file to be used from the configuration file +db_url : str = "sqlite:///" + CONFIG["database"]["name"] +print(f"Database file set to: {db_url}") + +# Creates the database engine (does not create the database file if it already exists) +engine = create_engine(db_url, echo=True) + +#Import Classes +from class_base import Base # Imports the base class required by SQLAlchemy +from class_client import Client +from class_account import Account +from class_transaction import Transaction + +# Create the tables in the database +Base.metadata.create_all(bind=engine) # Creates the tables in the database from the classes + +# Creates a session to interact with the database +Session = sessionmaker(bind=engine) # Creates a session to interact with the database +session = Session() # Creates a session object + +# Add sample data if enabled in the configuration file if the database is empty +if CONFIG["database"]["add_sample_data"] == "True": # Checks if sample data addition is enabled + if session.query(Client).count() == 0: # Checks if the database is empty + print(f"Sample data addition is disabled because the database is not empty.") + print(f"Adding sample data to new database file {CONFIG["database"]["name"]}.") + session.add(Client("f9a16945-b570-4153-ba63-413f2cc2768a", "Lucas Mathews", "24/08/1998", "17/04/2024", "Rachelsmolen 1, 5612MA, Eindhoven", "0612345678", "522499@student.fontys.nl", "7835062ec36ed529fe22cc63baf3ec18d347dacb21c9801da8ba0848cc18efdf1e51717dd5b1240f7556aca3947aa0722452858be6002c1d46b1f1c311b0e9d8", "Notes", True, True, "1, 2")) + session.add(Client("5be2a74d-d55c-4de6-85a1-2ed6a355f2cd", "Rigby", "16/03/2018", "06/05/2024", "Rachelsmolen 1, 5612MA, Eindhoven", "0612345678", "522499@cat.fontys.nl", "d3e7df3c78682fbb51e8c6110b3926349bb426bc9834c640cd666519901aef3dfab7822d66fb2dd9e39eb5a8492f6801c2e17ba4c16b8fbcd159c036fe27d8bd", "Is a cat", True, False, "3")) + session.add(Account("4c227b02-348c-4611-99a2-8967680cdee6", "Savings Account", "17/04/2024", "Savings", 3000, True, "Savings account", "1")) + session.add(Account("b9d9b801-eaab-45be-a4d1-1f7b0bbf798f", "Spending Account", "17/04/2024", "Spending", 150, True, "Spending account", "1")) + session.add(Account("f182f186-0a88-4f98-8a02-3a568c65aba7", "Cat Account", "06/05/2024", "Cat Account", 497, True, "Food savings", "2")) + session.add(Transaction("9d989788-f983-4590-8de2-9aa0c8bec7d2", "Deposit", 5000, "17/04/2024", "Initial Deposit", 1, "23542335")) + session.add(Transaction("153cee93-51c7-4114-b9ef-e307fbf0bf87", "Deposit", 100, "17/04/2024", "Initial Deposit", 2, "23542335")) + session.add(Transaction("4bec761a-0f36-452f-a48a-127dcf526e47", "Deposit", 500, "06/05/2024", "Initial Deposit", 3, "23542335")) + session.add(Transaction("227a2a9e-a13b-484b-b037-78deeeb0258c", "Withdrawal", 2000, "06/05/2024", "Uni Fees", 3, "Fontys University")) + session.add(Transaction("7248a706-29a8-478b-a674-c12ebf9a904a", "Withdrawal", 50, "06/05/2024", "Groceries", 3, "Aldi")) + session.add(Transaction("ba367c28-41e6-4f8a-9bfa-3819f7b89a58", "Withdrawal", 3, "06/05/2024", "Treats", 3, "ZooPlus")) + session.commit() + else: + print(f"The database is not empty, skipping sample data addition.") +else: + print(f"Sample data addition is disabled.") \ No newline at end of file diff --git a/manager.py b/manager.py new file mode 100644 index 0000000..7ca7f79 --- /dev/null +++ b/manager.py @@ -0,0 +1,249 @@ +# Lucas Mathews - Fontys Student ID: 5023572 +# Banking System Manager File + +from class_client import Client +from class_account import Account +from class_transaction import Transaction +from flask import jsonify +import hashlib # hashlib for password hashing +import datetime # datetime for timestamps +import uuid # uuid for unique identifiers + + +from database import * # Importing the database connection + +############## +### System ### +############## + +def timestamp(): # Returns the current timestamp + return (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + +def password_hash(password:str): # Converts a string to SHA512 hash + return hashlib.sha512(password.encode()).hexdigest() + +def generate_uuid(): # Generates a unique identifier for transactions + return str(uuid.uuid4()) + +def generate_uuid_short(): # Generates a short uuid + return str(uuid.uuid4())[:8] + +############## +### Client ### +############## + +def get_client(client_id:int): # Returns a specific client in the database + client = session.query(Client).filter_by(client_id=client_id).one_or_none() + if client is None: + return jsonify({"error": "Client not found"}), 404 + if client is not None: + return jsonify({"name": client.name, "birthdate": client.birthdate, "opening_timestamp": client.opening_timestamp, "address": client.address, "phone_number": client.phone_number, "email": client.email}), 200 + +def change_password(client_id, password:str, new_password:str): # Changes the password of a client + old_hash = password_hash(password) + new_hash = password_hash(new_password) + for client in session.query(Client).all(): + if client.client_id == client_id: + if client.hash == old_hash: + client.hash = new_hash + session.commit() + return "Password changed successfully.", 200 + return "Incorrect old password.", 400 + return f"client_id: {client_id} is not found.", 404 + +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, password_hash(password), notes, 1, 0, None) + session.add(new_client) + session.commit() + return f"New client has been added: name: {name}, uuid: {client_id} ", 200 + +def delete_client(client_id): # Deletes a client from the database + for client in session.query(Client).all(): + if client.client_id == client_id: + if client.accounts == None: + session.delete(client) + session.commit() + return f"client_id: {client_id} has been removed.", 200 + else: + return f"client_id: {client_id} has active accounts and can not be removed.", 400 + return f"client_id: {client_id} is not found.", 404 + + + + + +def login_user(email:str, password:str): + for client in session.query(Client).all(): + if client.email == email and client.password == password: + return f"Welcome {client.name}." + return "Invalid email or password." + +def logout_user(): + return "You have been logged out." + + + +def update_client(client_id, name, birthdate, address, phone_number, email, notes): + for client in session.query(Client).all(): + if client.client_id == client_id: + client.name = name + client.birthdate = birthdate + client.address = address + client.phone_number = phone_number + client.email = email + client.notes = notes + session.commit() + return f"client_id: {client_id} has been updated." + return f"Client ID: {client_id} is not found." + + + +############### +### Account ### +############### + +def get_account(account_id:int): # Returns a specific account in the database + account = session.query(Account).filter_by(account_id=account_id).one_or_none() + if account is None: + return jsonify({"error": "Account not found"}), 404 + if account is not None: + for account in account: + return jsonify({"description": account.description, "account_type": account.account_type, "balance": account.balance, "enabled": account.enabled, "notes": account.notes}), 200 + +def add_account(description:str, account_type, **kwargs): # Adds a new account to the database + account_id = generate_uuid_short() + notes = kwargs.get("notes", None) + new_account = Account(account_id, description, timestamp(), account_type, 0, 1, notes, None) + session.add(new_account) + session.commit() + return f"New account has been added: description: {description}, uuid: {account_id} ", 200 + +def delete_account(account_id): # Deletes an account from the database + for account in session.query(Account).all(): + if account.account_id == account_id: + if account.balance == 0: + session.delete(account) + session.commit() + return f"account_id: {account_id} has been removed.", 200 + else: + return f"account_id: {account_id} has a balance and can not be removed.", 400 + return f"account_id: {account_id} is not found.", 404 + + + + +def update_account(account_id:int, update:dict): + for account in session.query(Account).all(): + if account.account_id == account_id: + account.description = update["description"] + account.account_type = update["account_type"] + account.balance = update["balance"] + account.enabled = update["enabled"] + account.notes = update["notes"] + session.commit() + return f"account_id: {update['account_id']} has been updated." + return f"account_id: {update['account_id']} is not found." + + + + +################### +### Transaction ### +################### + +def get_transaction(transaction_id:int): # Returns a specific transaction in the database + transaction = session.query(Transaction).filter_by(transaction_id=transaction_id).one_or_none() + if transaction is None: + return jsonify({"error": "Transaction not found"}), 404 + if transaction is not None: + return jsonify({"transaction_type": transaction.transaction_type, "amount": transaction.amount, "timestamp": transaction.timestamp, "description": transaction.description, "account_number": transaction.account_number, "recipient_account_number": transaction.recipient_account_number}), 200 + +def transaction_history(account_id:int): # Returns all transactions for a specific account + result = session.query(Transaction).filter(Transaction.account_number == account_id) + return jsonify([{"transaction_id": transaction.transaction_id, "transaction_type": transaction.transaction_type, "amount": transaction.amount, "timestamp": transaction.timestamp, "description": transaction.description, "account_number": transaction.account_number, "recipient_account_number": transaction.recipient_account_number} for transaction in result]), 200 + +def add_transaction(amount:int, account_from, account_to, **kwargs): # Adds a new transaction to the database + transaction_id = generate_uuid() + for account in session.query(Account).all(): + if account.account_id == account_from: + if account.balance < amount: + return f"Account ID: {account_from} does not have enough funds to transfer {amount}.", 401 + account.balance -= amount + transaction_type = "withdraw" + session.commit() + if account.account_id == account_to: + account.balance += amount + transaction_type = "transfer" + session.commit() + description = kwargs.get("description", None) + new_transaction = Transaction(transaction_id, transaction_type, amount, timestamp(), description, account_from, account_to) + session.add(new_transaction) + session.commit() + return f"New transaction has been added: description: {description}, uuid: {transaction_id} ", 200 + + + +##################### +### Administrator ### +##################### + +def get_all_clients(): # Returns all clients in the database + clients = session.query(Client).all() + return jsonify([{"client_id": client.client_id, "name": client.name, "birthdate": client.birthdate, "opening_timestamp": client.opening_timestamp, "address": client.address, "phone_number": client.phone_number, "email": client.email} for client in clients]) + +def get_all_accounts(): # Returns all accounts in the database + accounts = session.query(Account).all() + return jsonify([{"account_id": account.account_id, "description": account.description, "open_timestamp": account.open_timestamp, "account_type": account.account_type, "balance": account.balance, "enabled": account.enabled, "notes": account.notes} for account in accounts]) + +def get_all_transactions(): # Returns all transactions in the database + transactions = session.query(Transaction).all() + return jsonify([{"transaction_id": transaction.transaction_id, "transaction_type": transaction.transaction_type, "amount": transaction.amount, "timestamp": transaction.timestamp, "description": transaction.description, "account_number": transaction.account_number, "recipient_account_number": transaction.recipient_account_number} for transaction in transactions]) + + + +def update_transaction(transaction_id, transaction_type, amount, description, account_number, recipient_account_number): + for transaction in session.query(Transaction).all(): + if transaction.transaction_id == transaction_id: + transaction.transaction_type = transaction_type + transaction.amount = amount + transaction.description = description + transaction.account_number = account_number + transaction.recipient_account_number = recipient_account_number + session.commit() + return f"Transaction ID: {transaction_id} has been updated." + return f"Transaction ID: {transaction_id} is not found." + +def apply_interest(account_id:int, interest_rate:float): + for account in session.query(Account).filter(Account.account_id == account_id): + if account.account_id == account_id: + account.balance += account.balance * interest_rate + session.commit() + return f"Interest has been applied to Account ID: {account_id}." + return f"Account ID: {account_id} is not found." + +def apply_fee(account_id:int, fee:float): + for account in session.query(Account).all(): + if account.account_id == account_id: + account.balance -= fee + session.commit() + return f"Fee has been applied to Account ID: {account_id}." + return f"Account ID: {account_id} is not found." + +def delete_transaction(transaction_id:int): + DELETE_TRANSACTION = "DELETE FROM transaction WHERE transaction_id=?" + from api import session, Transaction + for transaction in session.query(Transaction).all(): + if transaction.transaction_id == transaction_id: + input(f"Are you sure you would like permanenty delete transaction ID: {transaction_id}? WARNING: This action can not be reversed. (Y/N) ") + if input == "Y"or input == "y": + session.execute(DELETE_TRANSACTION, (transaction_id)) + print(f"Transaction ID: {transaction_id} has been removed.") + else: + return f"Transaction ID: {transaction_id} has NOT been removed." + return + return f"Transaction ID: {transaction_id} is not found." + + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f5cd4df --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +flask +connexion[swagger-ui]==2.14.2 +requests +sqlalchemy \ No newline at end of file