From 21fe74853c89aecb4c0928626ea1fa26c46bf017 Mon Sep 17 00:00:00 2001 From: Lucas Mathews Date: Fri, 7 Jun 2024 17:31:56 +0200 Subject: [PATCH] Finish implementing new transacitions --- api.yml | 14 +-- .../__pycache__/connection.cpython-312.pyc | Bin 14945 -> 15806 bytes application/connection.py | 28 ++++- application/new_transaction.py | 86 +++++++-------- application/session_data.json | 2 +- bank.db | Bin 704512 -> 704512 bytes manager.py | 103 +++++++++++------- 7 files changed, 135 insertions(+), 98 deletions(-) diff --git a/api.yml b/api.yml index d6ab2d6..aee0763 100644 --- a/api.yml +++ b/api.yml @@ -416,7 +416,7 @@ paths: description: Amount of transaction required: true schema: - type: integer + type: number format: float - name: account_id in: query @@ -430,12 +430,6 @@ paths: required: true schema: type: string - - name: description - in: query - description: Description of transaction - required: false - schema: - type: string - name: otp_code in: query description: OTP to verify @@ -443,6 +437,12 @@ paths: schema: type: integer format: int32 + - name: description + in: query + description: Description of transaction + required: false + schema: + type: string responses: '200': description: Successful operation diff --git a/application/__pycache__/connection.cpython-312.pyc b/application/__pycache__/connection.cpython-312.pyc index f9bdd65bf62058489a8656f14210ce61e140ffce..a127c35a78ce8f1015f2fe98a7aa70788ee1a9af 100644 GIT binary patch delta 1221 zcmZ`$Urbw77{90Gwzs|awt-SgfR+P==9ZQ+S~r1>ENg6yS&ZmJN@2}TKYvJ5m zw_z(rUqllj9%C@uVjuRV4>LTu2V?f{#c1wbXxf-Xvt%zC9}t%;N@6^R4ugxHzwp*SDhRWXO3YB%gkK;7iwa$r82VZ={)srNor1rKS>4ny#>AyKbjx z^eAk}ewn#y`5ZK2z~CFh0gEo+OgI7D*c|K<_GR<9*&VPo3UDPn%D;QjI?Y@FK}P3r z&fSIofIfDm>?k?#k8ugd`h9lCTaFXVlXxv*_c;|&X;$1DiM`bPLjH~1{G0+`dUXQI1x1aaqqNWXo~|Nwv8ZZ# z3{K3eIZcJ~8C5<9^)u=}B`}v)V1XzIDyp8Fo7ZCK7;Vx!nO83^sIsmqP(`SKU_q9P z2&qa8rf5uF5}}C_exK{l+*2?*D1&p z4&$Ffetas@->Q=E3-W27kjK8wY&CVUCoz0BByo(-U+?Iv_l?xsVZAf-8_%{#@7W#+ zjKj6Yt+%anhO1|j>&3rBjx68j#5L#o$YxMxYJ88$_f$^Y`JyK22C+o(e8-@yY6$QCsh?PssFJTOwVFfSbQinuN|*|bS-YAKGRtWE0)p-sPbwBQtGGWsSiZ(>+_|W_Pw&;(LVoG7IWvOAT zVX9%MVX0wGVV%QT%RYG_pXTIzO|8ja`1}|XCi`*QD|40zgUkYf6z&?%*$i`;Rx?8Q zj0`C}DZD9slP_q?GxAUVplus^i$RmM$QLMmOQa|@IkO-$HLoN-F*!NE6i8>L++s`2 z1u=`FfJ%z2L4*j15CsttAVL#Fn1TpxAmLVI4kRbn@kmdu)D@k)j)zN{>lS-rF2oR! zJw?eN0&G&$R_83t7kL0fR}eNuZjF*Ie9hfViTY_O*~Hg41How z%*;z1<$%mX%&bm4?1wnmL2Q0mXHMorIzrAY%!lk4f$YO9tUxlEVe$fHk#`sxI8OR3f0RZ8n Bh9&?2 diff --git a/application/connection.py b/application/connection.py index 784eb51..e64fca9 100644 --- a/application/connection.py +++ b/application/connection.py @@ -169,20 +169,38 @@ def update_account(account_id, otp_code:int, description=None, notes=None): print(f"RequestException: {e}") return {'success': False, 'message': "Could not connect to the server. Please try again later."} -def new_transaction(ammount, account_id, recipient_account_id, otp_code, description): +def new_transaction(account_id, description, amount, recipient_account_id, otp_code): """Creates a new transaction for the given account.""" try: with open('application\\session_data.json', 'r') as f: session_data = json.load(f) - if description == "": - description = None - params = {"account_id": account_id, "recipient_account_id": recipient_account_id, "amount": ammount, "description": description, "otp_code": otp_code} + # Prepare the query parameters + params = { + "amount": amount, + "account_id": account_id, + "recipient_account_id": recipient_account_id, + "otp_code": otp_code, + "description": description} + print(f"Params: {params}") response = requests.post(CONFIG["server"]["url"] + "/Transaction", cookies=session_data['session_cookie'], params=params) response.raise_for_status() + print(f"Response: {response.json()}") return response.json() + except requests.exceptions.HTTPError as e: + if e.response.status_code == 404: + return {'success': False, 'message': 'Account not found.'} + elif e.response.status_code == 403: + return {'success': False, 'message': 'Invalid OTP code.'} + elif e.response.status_code == 400: + return {'success': False, 'message': 'Invalid request. Please check the OTP code and other details.'} + else: + print(f"HTTPError: {e}") + return {'success': False, 'message': f"Unexpected error occurred. Error: {e}"} + except requests.exceptions.RequestException as e: print(f"RequestException: {e}") - return {'success': False, 'message': "Could not connect to the server. Please try again later."} + return {'success': False, 'message': f"Could not connect to the server. Error: {e}"} + def generate_otp(): """Generates a new OTP for the current client, which is sent by Email.""" diff --git a/application/new_transaction.py b/application/new_transaction.py index 5c3cfa8..bd7f87e 100644 --- a/application/new_transaction.py +++ b/application/new_transaction.py @@ -24,64 +24,64 @@ else: def submit_transaction(): """Submit a new transaction to the server.""" - account_id = account_combobox.get() - recipient_id = recipient_text.get("1.0", "end-1c") - amount = amount_text.get("1.0", "end-1c") - description = description_text.get("1.0", "end-1c") - - if not amount or not recipient_id or not account_id: - messagebox.showerror("Error", "Please fill in all fields.") + amount = amount_text.get("1.0", "end").strip() + account_id = account_combobox.get().strip() + recipient_id = recipient_text.get("1.0", "end").strip() + description = description_text.get("1.0", "end").strip() + otp_code:int = otp_text.get("1.0", "end").strip() + if not amount or not recipient_id or not account_id or not otp_code: + messagebox.showerror("Error", "Please fill in manditory fields.") return - - # Convert amount to float - try: + if verify_accounts(account_id, recipient_id, amount) is False: + return + if not otp_code.isdigit() or len(otp_code) != 6: + messagebox.showerror("Error", "Please enter a valid 6-digit OTP code.") + return + try: # Convert amount to float amount = float(amount) except ValueError: messagebox.showerror("Error", "Invalid amount entered.") return - - account_verification = verify_accounts() - if account_verification is False: - messagebox.showerror("Error", "Could not verify account IDs.") + try: # Convert otp code to int + otp_code = int(otp_code) + except ValueError: + messagebox.showerror("Error", "Invalid OTP code entered.") return - response = new_transaction(account_id, description, amount, recipient_id) - if response is None or "data" not in response: + response = new_transaction(account_id, description, amount, recipient_id, otp_code) + if response is None or "success" not in response: messagebox.showerror("Error", "Could not submit transaction.") return - transaction_id = response["data"] - otp = generate_otp(account_id, transaction_id) - if otp is None or "data" not in otp: - messagebox.showerror("Error", "Could not generate OTP.") + if not response['success']: # Check if the transaction was unsuccessful + messagebox.showerror("Error", response['message']) # Show the error message in a popup return - messagebox.showinfo("Success", f"Transaction submitted successfully. OTP: {otp['data']}") + if response['success']: # Check if the transaction was successful + messagebox.showinfo("Success", "Transaction submitted successfully.") + root.destroy() # Close the window + return -def verify_accounts(): - """Verify that the account IDs are valid.""" - try: - account_id = account_combobox.get() - recipient_id = recipient_text.get("1.0", "end-1c") - account = get_account(account_id) - recipient_id = get_account(recipient_id) - except requests.exceptions.RequestException as e: - messagebox.showerror("Error", f"Failed to get account details: {e}") + +def verify_accounts(account_id, recipient_id, amount): + """Verify that both account IDs are valid, and the from account has enough balance to make the transaction.""" + account = get_account(account_id) + if account is None or "data" not in account or "balance" not in account["data"]: + messagebox.showerror("Error", "Invalid account ID.") return False - if account is None or recipient_id is None or "data" not in account or "data" not in recipient_id: - messagebox.showerror("Error", "Server did not return the expected response.") + recipient = get_account(recipient_id) + if recipient is None or "data" not in recipient: + messagebox.showerror("Error", "Invalid recipient ID.") return False - if "account_id" not in account["data"]: - messagebox.showerror("Error", "Account ID not found.") + try: # Convert amount to float + amount = float(amount) + except ValueError: + messagebox.showerror("Error", "Invalid amount entered.") return False - if "account_id" not in recipient_id["data"]: - messagebox.showerror("Error", "Recipient Account ID not found.") - return False - amount = amount_text.get("1.0", "end-1c") - if account["data"]["balance"] < amount: # Check the client has the required balance - messagebox.showerror("Error", "Insufficient funds.") - return False - messagebox.showinfo("Success", "Accounts verified successfully.") + account_balance = account["data"]["balance"] + if account_balance < amount: + messagebox.showerror("Error", "Insufficient balance.") + return False return True def populate_accounts(client_id): @@ -156,7 +156,7 @@ otp_text = customtkinter.CTkTextbox(root, height=2, width=250) otp_text.pack(pady=5) # Create the submit button -submit_button = customtkinter.CTkButton(root, text="Submit", command=submit_transaction) +submit_button = customtkinter.CTkButton(root, text="Verify & Submit", command=submit_transaction) submit_button.pack(pady=5) # Populate accounts combobox with the given client_id diff --git a/application/session_data.json b/application/session_data.json index 63b4f0c..dc41138 100644 --- a/application/session_data.json +++ b/application/session_data.json @@ -1 +1 @@ -{"session_cookie": {"session": "0vm2PELvoXxLiOtpdLDR6M4-WHJq5xq5BX92b46UTkM"}, "client_id": "9755c18f"} \ No newline at end of file +{"session_cookie": {"session": "I5vE6-7HHC9fAildRSp-bxrPKTaglz4CWQgh1fdoFE4"}, "client_id": "9755c18f"} \ No newline at end of file diff --git a/bank.db b/bank.db index 12b90837e19a133e22fdfb12720356a2482cee96..dfc4413498eca5215881cdf85860376838a5a19a 100644 GIT binary patch delta 1931 zcma)+U2GIp7>4(pot^z@cc+RfkL5vb&qETlznFLCKUd&`B z`Q~}wdB5*`!^3Nbhu4nY0alK!co3{S+WXr!G1mf$(>t~w>_{I`HWf}3dZruj%?ecB z884oDrZKyFBOhwo`Sc)9BobK_Q59|iS+(|%T{ZsUxpn#Ev+aCfu&JZpcLw=Kb04(b zl0BPlPd4Nl;Q^@^J_YZUN3s{gQ%#_dEouQb0)i-}95K`LkZT(Zxs(#$u{^`@sZt3q zwtxm%?n!)^NdF}=7#s^vVK4-x$if=YyBDkqdntHb8h>DW>-ajiCEQ?vPy3SH;69O_ z0s||+Hu-Pxt2{3slK04)<(AZcsc%#7r}m{Dk17sKfp@!Fk{wXfH5O0@Bh&OFFRrB{ zhhl_#l>gtqeF2WA+5o5NET$kC%AfAUY zWgZD!Wnb8{7aqPJw2RaX+yFC!qHP8~86KU1jcIULjQs)kij8OB+GbP7F4s-udx43V zVQR?2u7^C`z&!d1$DyY3d^m9i1{KgDQfPYTy zjuP=~!$q#eqI`6VBg@lR6q1gOfO?GTw(>#+bS6X4DJ4swQ=~?v3e5i~ULKWJhaZnh zTk65rqT!(QO|xP9R)j!YBQ9dtWXLw7tPRuRku%M-iK7fxG80mCrNYdF{13=&Nt{lA z_3$2LL^Mo++r^qG`ECe*5uH*}a226J4FIAa7wnK5MfesnVp z;&{w*iHDW_Vc~80(w)gJFecJ-@(~G)!Ek(j{SUQ1_}ujk-Kh>Gt~w+Za?HR%I^!(R ziHnKHlou<<=jHJDeEQJ~peyP{<}p}zLbT6h-0;{;#>{~^@%!1#>|NEyddxGZuT$jt zet;NvJY-RqAdMTg&JEvVOjkx~Bk{$_T(MG^&4h*7?4_TJ4cXHP&EN!-1oWXax1f^r{~wdku7&kW-=Wrpm|kY$}|t|a&`^`d{+~}Q9{^2 z*lg0w5;03yWQ+=Zuj^Vdm!7gcfNwc)8a@jH|)!@ri)h;h# z>|%QT0y-&aMzlR*&+$KOolMs7%T<-$@GzID8RHkkuHh21$k47KezgFsC4Z}`E@eVv ze&YP){*97uMtv<;_x{xaAQo84Zo7^Ewio9oVt(0L!dkhW09jOEq8tloH(30&&3{dS PC?#z%fpFln+$->ZNH-iW delta 588 zcmXAlUr1A77{<^0o$q|-51z9j8Ae&drs6SaC1GTk5=9}{tWAdsO>tQvI@l_rl4-%N zlG&oy8|9eZ6cp;B&sdJrA3{XZ)m;!6-9#5*A{8OlsEgH>IV92l-X)QWj}9FD)G#(tq4r7#)BkuWh?wWnP46TC5~ zy7|{IIide&e=LyPxU!}&9_%NR^>79yn>65Qo1BFxpR&m=8Q$UW8tKQzc~XJ)cVhC1 zdD0OFA4*=a8+*OtF}TTRyy5_bEMC&VjRC)SE1BO@5{G7yLo*c*fghy=jpI;)mJ&UW zKS|KGJlH_zD1DDZU6L8s$7n7R*T>jT&{~Dp0@RVijzM%xO!svDhET z&1dd26F1+eCeNQ>YkR~NNTa;W9udM***Xd0=`7>vtbFntwB)puQ`l@Pon&7npS6{0 z4SwV5g0hI250Xj$o%152x#Py7!i`1s$7cuABZA|uqr`Y>H0YmnY>G+6Dc4l3&LeZ$ GZSofd0>%jd diff --git a/manager.py b/manager.py index 55a5303..be00771 100644 --- a/manager.py +++ b/manager.py @@ -228,7 +228,7 @@ def change_password(): if client.hash == hash_old_password: client.hash = hash_new_password session.commit() - delete_otp(client_id) + delete_otp(current_client_id) log_event(f"Password for client_id {client_id} has been updated by {current_client_id}.") return format_response(True, f"Password for client_id {client_id} has been updated."), 200 else: @@ -252,17 +252,17 @@ def get_accounts(client_id: str): ############### @login_required -def get_account(account_id:str): +def get_account(account_id: str): """Returns 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_owner = session.query(Account).filter_by(account_id=account_id).one_or_none().client_id + account = session.query(Account).filter_by(account_id=account_id).one_or_none() + if account is None: + return format_response(False, "Account not found."), 404 + account_owner = account.client_id if not is_admin and account_owner != current_client_id: return format_response(False, "You can only view your own client information."), 403 - account = session.query(Account).filter_by(account_id=account_id).one_or_none() - for account in session.query(Account).all(): - if account.account_id == account_id: - return format_response(True, "", account.to_dict()), 200 - return format_response(False, "Account not found."), 404 + return format_response(True, "", account.to_dict()), 200 + @login_required def add_account(client_id:str, description:str, account_type:str, **kwargs): @@ -285,34 +285,47 @@ def add_account(client_id:str, description:str, account_type:str, **kwargs): return format_response(True, f"New account has been added: account_id: {account_id}"), 200 @login_required -def update_account(account_id:str, otp_code:str, **kwargs): +def update_account(account_id: str, otp_code: str, **kwargs): """Updates an account in the database. If the account is not found, returns an error message.""" current_client_id, is_admin = get_current_client() + + # Verify OTP if not verify_otp(current_client_id, otp_code): return format_response(False, "Invalid OTP."), 400 - account_owner = session.query(Account).filter_by(account_id=account_id).one_or_none().client_id + + # Query the account once + account = session.query(Account).filter_by(account_id=account_id).one_or_none() + if account is None: + return format_response(False, "Account not found."), 404 + + account_owner = account.client_id + + # Check permissions if not is_admin and account_owner != current_client_id: - return format_response(False, "You can only view your own client information."), 403 - for account in session.query(Account).all(): - if account.account_id == account_id: - description = kwargs.get("description", None) - account_type = kwargs.get("account_type", None) - balance = kwargs.get("balance", None) - enabled = kwargs.get("enabled", None) - notes = kwargs.get("notes", None) - if description: - account.description = description - if account_type: - account.account_type = account_type - if balance: - account.balance = balance - if enabled: - account.enabled = enabled - if notes: - account.notes = notes - session.commit() - return format_response(True, f"account_id: {account_id} has been updated."), 200 - return format_response(False, "Account not found."), 404 + return format_response(False, "You can only update your own account information."), 403 + + # Update the account with provided kwargs + description = kwargs.get("description") + account_type = kwargs.get("account_type") + balance = kwargs.get("balance") + enabled = kwargs.get("enabled") + notes = kwargs.get("notes") + + if description is not None: + account.description = description + if account_type is not None: + account.account_type = account_type + if balance is not None: + account.balance = balance + if enabled is not None: + account.enabled = enabled + if notes is not None: + account.notes = notes + + # Commit the changes + session.commit() + return format_response(True, f"account_id: {account_id} has been updated."), 200 + ################### ### Transaction ### @@ -332,31 +345,37 @@ def get_transaction(transaction_id:int): return format_response(True, "", transaction.to_dict()), 200 @login_required -def add_transaction(amount:int, account_id, recipient_account_id, otp_code:int, **kwargs): +def add_transaction(amount: float, account_id: str, recipient_account_id: str, otp_code: int, description: str): """Adds a new transaction to the database. If the account is not found, returns an error message.""" + print(f"Adding transaction: amount: {amount}, account_id: {account_id}, recipient_account_id: {recipient_account_id}, otp_code: {otp_code}, description: {description}") 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 - otp_verified = verify_otp(current_client_id, otp_code) # Verify if the OTP is correct + otp_verified = verify_otp(current_client_id, otp_code) if not otp_verified: return format_response(False, "Invalid OTP."), 400 + transaction_id = generate_uuid() - for account in session.query(Account).all(): - if account.account_id == account_id: - account_from = account - if account.account_id == recipient_account_id: - account_dest = account - if account_from.balance < amount: # Check if account has enough funds + account_from = session.query(Account).filter_by(account_id=account_id).one_or_none() + account_dest = session.query(Account).filter_by(account_id=recipient_account_id).one_or_none() + + + if account_from is None or account_dest is None: + return format_response(False, "Account not found."), 404 + if account_from.balance < amount: return format_response(False, "Insufficient funds."), 400 - account_from.balance -= amount # Perform the transaction + delete_otp(current_client_id) + # Perform the transaction + account_from.balance -= amount account_dest.balance += amount transaction_type = "transfer" session.commit() - description = kwargs.get("description", None) # Create the transaction record + # 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 + + return format_response(True, f"New transaction has been added: transaction_id: {transaction_id}"), 200 @login_required def transaction_history(account_id:int):