From 4d44c8dcd6ec540508346bb270cdf0796cab50bf Mon Sep 17 00:00:00 2001
From: Roi Solomon <roisol144@gmail.com>
Date: Sun, 3 Nov 2024 21:52:11 +0200
Subject: [PATCH] Deposit, Withdraw & Transfer Handlers

---
 .env                                  |   3 +-
 .github/workflows/ci.yml              |   6 +-
 __pycache__/auth_utils.cpython-39.pyc | Bin 1612 -> 2103 bytes
 __pycache__/users.cpython-39.pyc      | Bin 3697 -> 4037 bytes
 auth_utils.py                         |  21 ++-
 bank_accounts.py                      | 195 ++++++++++++++++++++------
 db.py                                 |  46 ++++--
 exceptions.py                         |   2 +-
 requirements.txt                      |   6 +-
 server.py                             |  54 ++-----
 test_app.py                           |  95 +++++++------
 users.py                              |  14 +-
 12 files changed, 288 insertions(+), 154 deletions(-)

diff --git a/.env b/.env
index e93aba1..4fb7102 100644
--- a/.env
+++ b/.env
@@ -3,4 +3,5 @@ FERNET_KEY=cYLeyg4Ljx31xEcdd9kWqSnIgo3RkusJEmOwZcKqrNM=
 DATABASE_URL = postgres://postgres:solo6755@postgres:5432/postgres  
 TEST_DATABASE_URL = postgres://postgres:solo6755@postgres:5432/test_db # fix it
 FLASK_ENV = development
-TOKEN_EXPIRATION_HOURS = 1
\ No newline at end of file
+TOKEN_EXPIRATION_HOURS = 1
+SALT = 52c9f9f1cf12deafde09e6257abbd06b
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 673292f..69e9eb3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -54,9 +54,10 @@ jobs:
       # Install dependencies
       - name: Install dependencies
         run: |
-          source venv/bin/activate
+          python -m venv venv          
+          source venv/bin/activate     
           pip install -r requirements.txt
-          
+      
       - name: Lint with Flake8
         run: |
           source venv/bin/activate
@@ -64,4 +65,5 @@ jobs:
       
       - name: Tests
         run: |
+          source venv/bin/activate
           python test_app.py
diff --git a/__pycache__/auth_utils.cpython-39.pyc b/__pycache__/auth_utils.cpython-39.pyc
index 8f5f708417266d4470e39d39e17b4d020d69065a..90cd73883104b18aab47417f291b3e3676a996ff 100644
GIT binary patch
delta 1154
zcma)4O>f&q5Zzf)BqdR_6iIgck%n%Ox@sH62-2X3ph#LMhqf?^+6WL`1X{b6=uk^z
zxpFK+fq|&K6zIX=9}s^-4?XwZKTra_7yS!Gi_XeQ5%^RRe6u_Au-tj`eszDYdO@X9
zBKUY>XCRzQZwvms{OI!wh>G1JBg~9SqjI+l1lNVg9aXv&AiO*yh|aM$Utv}T-Rg2}
z<GRLc48}zMz;7^T2Csovcx_(K$PBs--dr+^8Q^}E+sAA8-DD+h&(~Qw1B@FPX%8rK
ze>Cv773O_Mx|?i;RWWX{8oPn<7OS%c#&;O(lUDQckN4ej6bG!wV#UR2T84Lb&)ZKs
z-|lq|dS7*(`yy8UQ9KsR4~376P{v}!h4N1W83u<DPuf4-g;l6Ks6}oZzf`%!ML%X-
z+UYa84eRL{-CNtjYT|0WXagLee_Mqr$j$V3dJ9zg7k%{j8$fA*C}VE2LIyydqjdYk
zB#Inc6Y*9Rg?m%oU2t0Vo?J<1#;0`;e{k*{#4ot;J7*`M<SfmNZLfj(++uurucG;Q
z=O(7)nuhiNU_;Z~Q!c}!Nl)oh?xZJX14`-l<~}sj-^|^MYwT5QR!&Y-FX;DCCe;(;
z(IJ=j@ub8=AmD*~4`UznZuuA(b@dDUlBWoY%z;4$uV4y{pbIt>WmLOB8B?On8BIRM
zj4`{=slwcvk}0f_xt);==1xXl(J94=ntQpq|K#g~^A9xspXr_ZPp)q6U)|gvjT7Y`
zazF4BB||Z2w~9H9liV6`w8+J2UV0WpW8RT6mU$^mLXoII^m%R|KevZLGK|8*+)9SQ
z{ZAgsCU%z>hy09%1D>dsA-8qLOoB+|P0i-)nt0bIQrD~{=k6PYA8P6Jya|-hQ4U3<
zt03=TrgdBDiOX90WubK>@96c12xx(Y&1C^M-TGp1@jwPA!%6#yOTm@2G|gTbJvXpt
zX=`%ns@{9SCvpc9i@v0<*+wXobkbi64~Cn%a$AEQ`Qk2bHM6kJ!eh>(QOw2>|4f@A
jX(M0<Pz6_ag0G9OMP2F|x-%E57%5c$u}r7vR2=hPW40a(

delta 658
zcmYjO&2G~`5Z>{B9NTpplfTj?E#i<?LKSe}5Fv!b4TVEM2n(qy-AzcE*wMx<xIzL6
zt{keBxKyC{892ZjaN!Aj;7lI^#B2yCyV`GNzxifoM*GG8?sx~TYZF`_9&Pro8fV^R
z_<FW`_q8@EjP1|{!mMF&=1@I@&|UZj`W~~HH(SZbTL^u&x`>z=;C+pm!xEl=TFjhX
zqJ<35mNU{<sQq3ALW|USN5TqqsfV^oSE!G+Mxjeus~6af*o1BI02-e^K^aOWGPyc@
zk>m!84r9u=A-*YFP!$(SrxIXv>L3s$01K2~hVBEei;lVhZ^TG#-+u~3?nIqwRDT6j
ztC4d;jgM*uK%Njvx<n%h85-&7k`jmc7u}m{T2_a9;-`A6;bO7V%ES1GMa_FJCj-uC
zvlsK`@s#mq4&sKk<&{vA8<Z{jeWu-MSCHam3Dy5llQQRo@j)-$Pvnam$V}x>1Qb|<
z0BrD}EPiNR2*h=Lueg*f#`^mDj!e+z2_En(=(Orwmgl~3%vL8?;%Qhq<mn`d`}|<?
zJiXRqJYosgQIwl3IvMad%GEwn22!E@H@<(wQZ75;O(|3dQNm4eZr*%cmt;eNEVney
qytnyGR(l-N;}N?JsF-deSok|WIEWTF3U2g$6@Lu;--c!tEcXvWS&U`?

diff --git a/__pycache__/users.cpython-39.pyc b/__pycache__/users.cpython-39.pyc
index 46a8e2607bc6458e934809b50c658b56acf33d96..a3c4367ccbc5fe61170384a84b9b4391f3e9f645 100644
GIT binary patch
delta 1214
zcmZ8g&2Jk;6rY*>%zC|bz8pJol5G_TK|mrkAVK<}mYODj%psIQK&p&q8##4$)8}>C
zSZjPJN05RBacL^q;=~0Bapu?_koX5I4oIBB0ffXUQY+qDLipIx{^srb+&Awxzn31)
zdev;!Vfd84pWECT{l?oA{1>$=2J;*7@ICf{=s2BB;`ByjuFpA3rQyU%m>elG#8LH`
zc6Q|poyFt(eD7n<Ixc7JS7NJY%dtee&*XUG-e(W=G&<AE_ZergCbKeM=l8grP`_zY
z^Ai~R2xGGt`)q7&*|H#ua`IDg>^qhEmekAo>FMLTX?3&6<n)1#d1h{kb+$13<sF^d
zZS!*Z>RM$*ePVp4w#_SQ)tnnS^47vQfUw%|%K#A^{-M4$XLv#VVD=`#z!MnQ5H%Vf
zR%^EgL*=`*U?;9eq3T$>nGJF6odFNNVsKtPv2LH{4lYykAM3i!ZSHD0p2vMo<haM5
z*5Kl|`}Vt>PpQM9bNM6hS~CE@62<;&Q7e>(pL52J)QVGbrm-r_ARMWx^VZTbN(&l{
z)4%8~41#myaQ*}?kj35%;u~arL;d8O?XID&?Jkm{x!8y{>tPKwCy#{^&G%J&o3CT#
znDuy{b#Qi2l00F%yjhZhc6crpQj>am)&`wt!g`wIecnMolmtqm?Z$e7lj~^LS-7uv
zS#0cu9EXadqHr?}w{8lPy~oxW62sg(IJVPZZ8;>Jbg<-|Nxj{Fc@ORVh(yp&h>-n{
zzxTKQbw>Fm>vr^y^mP<yUt&uv(e^pK6g$04qRCUp644i_#@%vzS?t`1J+$PC40|w<
zr>Ep_!jY#ViGeu!z#yh#D`SZ%#}5Q@D^F*UHaC%$FVMHd<`#;TZaOZ=<MF~ILfbEI
zSL+SG8cKhs+HBs5K>Ae(d<bs0>JZ2mpg_Kt31}#A1>tR&BJqR|?b4;tZ-v3#ouC#6
z(nl&p;77Gu3or$g(Rq+f)2p8|?;E#*5H6`z_o<~EXq4y-flhFhfCvCCL55(SfPNO1
zRo8vK`wWT;7Q9FrBHv%#tdc|o+;CD_-H7Yk0lZ1t|1r`??EzYvL}4auZH(sxUJ@a=
zi)##D57DFRAqseTTwdUU))P}R#be!oiaL}1b@n2qCrU$w;GaUNbU`h8E7r2doubG8
E1e3uK*Z=?k

delta 903
zcmZ9K&rcIU6vt;~e|Fkkp|r(9f%@B;7*LECe#BpBBoPk;e-IPWFiTT@?CNwtY_@XX
zs)S^`0m(L=I2zBM)Pw(l>B%b>6BABGjc-<h3A5SHym|BH<vZ`q=e{2Wx9m6+!6+A<
z%3}LFw?gnQxk6gkg=k+#Pe_9{k|AYi8P)c~q?$J|H-*L=)>g&qn6=N5hiDRoG)l8{
zXoZM%<Pk6<pKF6_8#)r%P5fd5qecp&`UPRl+bqj+tn)R&=x81jJw^;7U#2Dx<^cp5
za**LGWN;wEWno1&J05CVh~-0P3+?Dle2}RtR@AK&{mUdT>76|XdSX>(9<jo<4!V2h
z$RsNDzV&pR{psHIg~$E!VsVV~fbZ>NgyfR(K?de2Su)28yLyXM8Sj8f@r*(iu=fQ}
z>XaYNsI8S4Tb=cuifZ6<1KhG<tt4C6n<i5ab`c+zf2?OG2*qifCZ<;Wts6GBaf-BI
z7oP^^@L7$IzTdPTVcaA462rN=J5}>|aUu}K+rhHWs&7D4T@KL#?N$m3fVwQx`vZeY
zh89a|>C14452=9RLyUmcvMZjLQM>o#H#)d>3w-rdzjD<27lTUGp9Rn1Fu}6mzX^Zs
z6+oZ}H&Fvx8xe+^xPfDffJKBB!c%Byp~hE*9vPv=G$=P|5?Gt_W@N!hUD)8)^9Ujh
zaVHT`W`r2JXofn_+A-ojCYc=?EU`_XM^yDlBw3QB;?6p1&EU0_+==+QJSPpuquzqg
zuYpG1B?IS7I>j}$IH_<$fyz(L#qv32o>$NTC6ixKwrV8LDX6PX)3_8fqPpa@3=u4N
z{?R%baYO}zYrOzOY)s*^Nk*343?7#&?)Sb?*x;(fd|cswD`IOzt{)q+uDY1!UHliN
C63f&8

diff --git a/auth_utils.py b/auth_utils.py
index 0b70144..21eee39 100644
--- a/auth_utils.py
+++ b/auth_utils.py
@@ -4,11 +4,19 @@
 from functools import wraps
 import logging
 import os
+import hashlib
+from dotenv import load_dotenv
 from cryptography.fernet import Fernet
+load_dotenv()
 
 SECRET_KEY = os.environ.get('SECRET_KEY')
 # Keys
 fernet_key = os.environ.get('FERNET_KEY')
+
+# Check if fernet_key is None and raise an error if it is
+if fernet_key is None:
+    raise ValueError("FERNET_KEY not found in environment variables.")
+
 fernet = Fernet(fernet_key.encode())
 TOKEN_EXPIRATION_HOURS = int(os.environ.get('TOKEN_EXPIRATION_HOURS'))
 
@@ -32,7 +40,7 @@ def verify_token(token):
         logging.debug(f"Authentication token valid for user {payload['sub']}")
         return payload['sub']
     except jwt.ExpiredSignatureError:
-        logging.debug(f"Token Expired for user {payload['sub']}.")
+        logging.debug("Token Expired.")
         return None
     except jwt.InvalidTokenError:
         logging.debug("Invalid token was supplied.")
@@ -42,6 +50,17 @@ def verify_token(token):
 def encrypt_account_number(account_number):
     encrypted_account_number = fernet.encrypt(account_number.encode())
     return encrypted_account_number
+
+def hash_account_number(account_number):
+    salt = os.getenv('SALT')
+    if salt is None:
+        raise ValueError("SALT not found in environment variables.")
+    
+    if not isinstance(account_number, str):
+        raise ValueError("Account number must be a string.")
+    
+    hashed_account_number = hashlib.sha256((account_number + salt).encode()).hexdigest()
+    return hashed_account_number
       
     
     
\ No newline at end of file
diff --git a/bank_accounts.py b/bank_accounts.py
index 727e645..9c4085a 100644
--- a/bank_accounts.py
+++ b/bank_accounts.py
@@ -1,14 +1,15 @@
 from flask import Blueprint, request, jsonify, g
 import logging
 from flask_bcrypt import Bcrypt
-from db import get_db_connection, get_user_by_email, check_is_valid_user_id, check_is_valid_account_number, get_account_id_by_user_id
+from db import get_db_connection, get_accounts_numbers_by_user_id, check_is_valid_user_id, check_is_valid_account_number, get_accounts_id_by_user_id
 from exceptions import UserNotFoundError, DatabaseConnectionError, InsufficientFundsError, AccountNotFoundError
 from uuid import uuid4
 import datetime
 import os
-from auth_utils import encrypt_account_number
+from auth_utils import encrypt_account_number, hash_account_number
 import hashlib
 from enum import Enum
+import random
 
 
 # logging config
@@ -30,8 +31,6 @@ class Status(Enum):
     SUSPENDED = 'SUSPENDED'
     CLOSED = 'CLOSED'
     
-
-
 # Create a Blueprint for users
 bank_accounts_bp = Blueprint('bank_accounts', __name__)
 bcrypt = Bcrypt()
@@ -62,43 +61,50 @@ def get_bank_accounts():
         ]
         
         return jsonify(accounts_list), 200
-        
+    
+    except DatabaseConnectionError as e:
+        return jsonify({'error': 'Internal Error'}), 500
     except Exception as e:
         logging.debug("General Error occured.")
         return jsonify({'error': 'Internal Error'}), 500
     
-    except DatabaseConnectionError as e:
-        return jsonify({'error': 'Internal Error'}), 500
+
     
 @bank_accounts_bp.route('/bank_accounts', methods=['POST'])
 def create_bank_account():
     data = request.get_json()
     user_id = data['user_id']
+    currency = data['currency']
+    account_type = data['account_type']
     
     try:
         user_id = check_is_valid_user_id(str(data['user_id']))
         if not user_id:
             raise UserNotFoundError("Invalid user id.")
+        if not currency: # Might need to validate it in the case of an unexpected value
+            currency = Currency.USD.value
+        if not account_type:
+            account_type = AccountType.CHECKINGS.value # Might need to validate it in the case of an unexpected value
 
         # Required params
         account_id = str(uuid4())
-        account_number = str(int(uuid4()))[:6]
+        account_number = str(random.randint(100000, 999999))
         logging.debug(f"Account number: {account_number}")
-        salt = os.urandom(16).hex()
+        salt = str(os.getenv('SALT'))
         hashed_account_number = hashlib.sha256((account_number + salt).encode()).hexdigest()
         encrypted_account_number = encrypt_account_number(account_number).decode('utf-8')
-        currency = Currency.USD.value 
         balance = 0  # change it to other default value that will be config
-        account_type = AccountType.CHECKINGS.value
         created_at = datetime.datetime.now()
         status = Status.ACTIVE.value
         
         cursor, conn = get_db_connection()
+        cursor.execute("BEGIN;")
         
         cursor.execute("""
             INSERT INTO bank_accounts (id, user_id, account_number, balance, type, currency, created_at, status, hashed_account_number) 
             VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;
         """, (account_id, user_id, encrypted_account_number, balance, account_type, currency, created_at, status, hashed_account_number))
+        conn.commit()
         cursor.close()
         conn.close()
             
@@ -107,71 +113,178 @@ def create_bank_account():
     except UserNotFoundError as e:
         return jsonify({'error' : 'User not found'}), 404
     except Exception as e:
+        conn.rollback()
         return jsonify({'error': 'Internal Error'}), 500
     
     return jsonify({'account_id': account_id, 'account_number': encrypted_account_number, 'created_at': created_at}), 201
 
     
+
+@bank_accounts_bp.route('/bank_accounts/deposit', methods=['POST'])
+def deposit():
+    data = request.get_json()
+    amount = data['amount']
+    transfer_id = str(uuid4())
+    account_number_for_deposit = data['account_number']
+    current_user_id = g.current_user_id
+    transaction_type = "DEPOSIT"
+    user_hashed_accounts_numbers_list = get_accounts_numbers_by_user_id(current_user_id)
+    hashed_account_number = hash_account_number(account_number_for_deposit)    
+    
+    if not amount or not isinstance(amount, (int, float)):
+        raise ValueError("Invalid/Missing amount.")
+    if not user_hashed_accounts_numbers_list:
+        raise AccountNotFoundError("User has no bank accounts.")
+    
+    if hashed_account_number not in user_hashed_accounts_numbers_list:
+        raise AccountNotFoundError("No account found.")
+    
+    try:
+        if amount <= 0:
+            return jsonify({'error': 'Amount must be positive.'}), 400
+        
+        cursor, conn = get_db_connection()
+        cursor.execute("BEGIN;")
+        
+        cursor.execute("UPDATE bank_accounts SET balance = balance + %s WHERE hashed_account_number = %s", (amount, hashed_account_number))
+        cursor.execute("""
+                       INSERT INTO transactions (id, from_account, to_account, amount, type, transaction_date) 
+                       VALUES (%s, %s, %s, %s, %s, %s)""",
+                       (transfer_id, hashed_account_number, hashed_account_number, amount, transaction_type, datetime.datetime.now()))
+        conn.commit()
+        return jsonify({'message': 'Deposit Successfully!'}), 200
+        
+    except ValueError as e:
+        conn.rollback()
+        return jsonify({'error': str(e)}), 400
+    
+    except AccountNotFoundError as e:
+        conn.rollback()
+        return jsonify({'error': str(e)}), 400
+    
+    except Exception as e:
+        conn.rollback()
+        return jsonify({'error': 'Deposit failed.'}), 400    
+    
+    finally:
+        if cursor:
+            cursor.close()
+        if conn:
+            conn.close()
+            
+@bank_accounts_bp.route('/bank_accounts/withdraw', methods=['POST'])
+def withdraw():
+    data = request.get_json()
+    amount_to_withdraw = data['amount']
+    transaction_type = "WITHDRAW"
+    account_number_for_withdrawal = data['account_number']
+    transfer_id = str(uuid4())
+    current_user_id = g.current_user_id
+    user_hashed_accounts_numbers_list = get_accounts_numbers_by_user_id(current_user_id)
+    hashed_account_number = hash_account_number(account_number_for_withdrawal)
+    
+    if not amount_to_withdraw or not isinstance(amount_to_withdraw, (int, float)):
+        raise ValueError("Invalid/Missing amount.")
+    if not user_hashed_accounts_numbers_list:   
+        raise AccountNotFoundError("User has no bank accounts.")
+    
+    if hashed_account_number not in user_hashed_accounts_numbers_list:
+        raise AccountNotFoundError("No account found.")
+    
+    try:
+        if amount_to_withdraw <= 0:
+            return jsonify({'error': 'Amount must be positive.'}),400
+        cursor, conn = get_db_connection()
+        
+        if hashed_account_number not in user_hashed_accounts_numbers_list:
+            raise AccountNotFoundError("No account found.")
+        
+        cursor.execute("SELECT balance FROM bank_accounts WHERE hashed_account_number = %s", (hashed_account_number,))
+        account_to_withdraw_from = cursor.fetchone()
+        if account_to_withdraw_from['balance'] < amount_to_withdraw:
+            raise InsufficientFundsError("Insufficient Funds In Account.")
+        
+        cursor.execute("BEGIN;")
+        cursor.execute("UPDATE bank_accounts SET balance = balance - %s WHERE hashed_account_number = %s", (amount_to_withdraw, hashed_account_number))
+        cursor.execute("""
+                       INSERT INTO transactions (id, from_account, to_account, amount, type, transaction_date) 
+                       VALUES (%s, %s, %s, %s, %s, %s)""",
+                       (transfer_id, hashed_account_number, hashed_account_number, amount_to_withdraw, transaction_type, datetime.datetime.now()))
+        conn.commit()  
+        return jsonify({'message': 'Withdraw Succeded!'}), 200   
+          
+    except InsufficientFundsError as e:
+        return jsonify({'error': str(e)}), 400
+    
+    except AccountNotFoundError as e:
+        return jsonify({'error': str(e)}), 400
+    
+    except ValueError as e:
+        return jsonify({'error': str(e)}), 400
+
+    except Exception as e:
+        logging.debug(f"Error: {e}")
+        return jsonify({'error' : 'General Erorr'}), 500
+    
+    finally:
+        if cursor:
+            cursor.close()
+        if conn:
+            conn.close()
+
+    
+
 @bank_accounts_bp.route('/bank_accounts/transfer', methods=['POST'])
 def transfer():    
     data = request.get_json()
     transfer_id = str(uuid4())
     to_account_number = data.get('to_account_number')
+    from_account_number = data.get('from_account_number')
+    hashed_account_number_of_reciever = hash_account_number(to_account_number)
+    hashed_account_nunber_of_sender = hash_account_number(from_account_number)
+    tansaction_type = "TRANSFER"
     amount = data.get('amount')
-    transaction_type = data.get('transaction_type')
-    logging.debug(f"Parsed data: transfer_id={transfer_id}, to_account_number={to_account_number}, amount={amount}, transaction_type={transaction_type}")
-
     
     # Validate input data
     if not amount or not isinstance(amount, (int, float)):
         return jsonify({'error': 'Invalid amount'}), 400
-    if not transaction_type or transaction_type not in ['WITHDRAW', 'DEPOSIT']:
-        return jsonify({'error': 'Invalid transaction type'}), 400
     
     try:
         if amount <= 0:
             return jsonify({'error': 'Amount must be positive'}), 400
         
-        from_account_id = get_account_id_by_user_id(g.current_user_id)
-        logging.debug(f"From account ID: {from_account_id}")
-        
-        if not from_account_id:
-            raise AccountNotFoundError("Account not found for the current user")
+        if not to_account_number:
+            raise ValueError("Account number is required for deposit.")
         
+        # Establish db connection and transaction
         cursor, conn = get_db_connection() 
-                
+        cursor.execute("BEGIN;")
+        
         # Check if the sender's account exists
-        cursor.execute("SELECT balance FROM bank_accounts WHERE id = %s", (from_account_id,))
+        # Note to self - Check how to auth works here, how its possible to take money from account - will it auth first ? 
+        cursor.execute("SELECT balance FROM bank_accounts WHERE hashed_account_number = %s", (hashed_account_nunber_of_sender,))
         sender_account = cursor.fetchone()
         if not sender_account:
             raise AccountNotFoundError("Sender's account not found")
 
         sender_balance = sender_account['balance']
-        
         if sender_balance < amount:
             raise InsufficientFundsError("Insufficient funds.")
         
-        if transaction_type == 'WITHDRAW':
-            to_account_id = None
-            cursor.execute("UPDATE bank_accounts SET balance = balance - %s WHERE id = %s", (amount, from_account_id))
-        
-        elif transaction_type == 'DEPOSIT': 
-            if not to_account_number:
-                raise ValueError("Account number is required for deposit.")
-            
-            receiver_account_id = check_is_valid_account_number(to_account_number)
-            if not receiver_account_id:
-                raise ValueError("Invalid receiver account number")
+        # Validate Bank Account Number - 
+        cursor.execute("SELECT balance FROM bank_accounts WHERE hashed_account_number = %s", (hashed_account_number_of_reciever,))
+        account_to_transfer_money_to = cursor.fetchone()
+        # Check if no account found
+        if not account_to_transfer_money_to:
+            raise AccountNotFoundError("No Account Found.")
             
-            cursor.execute("UPDATE bank_accounts SET balance = balance - %s WHERE id = %s", (amount, from_account_id))
-            cursor.execute("UPDATE bank_accounts SET balance = balance + %s WHERE id = %s", (amount, receiver_account_id))
-            to_account_id = receiver_account_id
-        
+        cursor.execute("UPDATE bank_accounts SET balance = balance - %s WHERE hashed_account_number = %s", (amount, hashed_account_nunber_of_sender))
+        cursor.execute("UPDATE bank_accounts SET balance = balance + %s WHERE hashed_account_number = %s", (amount, hashed_account_number_of_reciever))        
         cursor.execute("""
                        INSERT INTO transactions (id, from_account, to_account, amount, type, transaction_date) 
                        VALUES (%s, %s, %s, %s, %s, %s)""",
-                       (transfer_id, from_account_id, to_account_id, amount, transaction_type, datetime.datetime.now()))
-        
+                       (transfer_id, hashed_account_number_of_reciever, hashed_account_nunber_of_sender, amount, tansaction_type, datetime.datetime.now()))
+        conn.commit()
         return jsonify({'message': 'Transfer successful'}), 200
 
     except (DatabaseConnectionError, InsufficientFundsError, ValueError, AccountNotFoundError) as e:
diff --git a/db.py b/db.py
index 10aada0..0a6e7dc 100644
--- a/db.py
+++ b/db.py
@@ -15,19 +15,17 @@
 
 
 def get_db_connection():
-    if os.getenv('FLASK_ENV') == 'TEST':
-        db_url = os.getenv('TEST_DATABASE_URL')
-    else:
-        db_url = os.getenv('DATABASE_URL')
+    db_url = os.getenv('DATABASE_URL')
         
     try:
         conn = psycopg2.connect(db_url)
-        conn.autocommit = True 
         cursor = conn.cursor(cursor_factory=RealDictCursor)
+        conn.set_isolation_level('SERIALIZABLE')
         return cursor, conn
     except Exception as e:
         logging.error("Failed to connect to DB.", exc_info=True)
-        raise DatabaseConnectionError("Could not connect to DB.")    
+        raise DatabaseConnectionError("Could not connect to DB.") 
+       
 def get_user_by_email(email):
     try:
         cursor, conn = get_db_connection()
@@ -86,7 +84,6 @@ def check_is_valid_account_number(account_number):
         cursor, conn = get_db_connection()
         if cursor is None or conn is None:
             raise DatabaseConnectionError("Failed to connect")
-        # Here change the encrypted with the hashed account number - hash it and then search for it inside the db
         hashed_account_number = hashlib.sha256(account_number.encode()).hexdigest()
         logging.debug(f"Hashed account number: {hashed_account_number}")
         cursor.execute("SELECT * FROM bank_accounts WHERE hashed_account_number = %s", (hashed_account_number,))
@@ -111,19 +108,41 @@ def check_is_valid_account_number(account_number):
         if conn:
             conn.close()
 
-def get_account_id_by_user_id(user_id):
+def get_accounts_id_by_user_id(user_id):
     try:
         cursor, conn = get_db_connection()
         if cursor is None or conn is None:
             raise DatabaseConnectionError("Failed to connect to db.")
         
         cursor.execute("SELECT id FROM bank_accounts WHERE user_id = %s", (user_id,))
-        result = cursor.fetchone()
+        results = cursor.fetchall()  # Fetch all results
         
-        if result:
-            return result['id']
-        else:
-            return None
+        return [result['id'] for result in results] if results else None
+    
+    except DatabaseConnectionError:
+        logging.error(f"Database connection error: {user_id}", exc_info=True)
+        return None
+    except AccountNotFoundError:
+        logging.error(f"Account not found for user: {user_id}", exc_info=True)
+        return None
+    finally:
+        if cursor:
+            cursor.close()
+        if conn:
+            conn.close()
+            
+            
+def get_accounts_numbers_by_user_id(user_id):
+    try:
+        cursor, conn = get_db_connection()
+        if cursor is None or conn is None:
+            raise DatabaseConnectionError("Failed to connect to db.")
+        
+        cursor.execute("SELECT hashed_account_number FROM bank_accounts WHERE user_id = %s", (user_id,))
+        results = cursor.fetchall()  # Fetch all results
+        
+        return [result['hashed_account_number'] for result in results] if results else None
+    
     except DatabaseConnectionError:
         logging.error(f"Database connection error: {user_id}", exc_info=True)
         return None
@@ -135,3 +154,4 @@ def get_account_id_by_user_id(user_id):
             cursor.close()
         if conn:
             conn.close()
+
diff --git a/exceptions.py b/exceptions.py
index 1ab2e9f..00d63a6 100644
--- a/exceptions.py
+++ b/exceptions.py
@@ -8,7 +8,7 @@ def __init__(self):
         super().__init__("Failed to connect to the database.")
         
 class TokenVerificationError(Exception):
-    def __init__(self):
+    def __init__(self,message):
         super().__init__("Token verification failed.")
         
 class InsufficientFundsError(Exception):
diff --git a/requirements.txt b/requirements.txt
index 6cbb840..7fde641 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,8 @@
-cryptography==43.0.1
+cryptography==43.0.3
+Faker==30.8.2
 Flask==3.0.3
 Flask_Bcrypt==1.0.1
 psycopg2_binary==2.9.9
 PyJWT==2.9.0
 PyJWT==2.9.0
-pytest==8.3.3
-flake8==7.1.1
+python-dotenv==1.0.1
diff --git a/server.py b/server.py
index 74a05ab..6b26c95 100644
--- a/server.py
+++ b/server.py
@@ -45,13 +45,6 @@ def log_reponse_info(response):
     ('/users/register', 'POST'): 'register'
     }
 
-closed_routes = {
-    ('/users', 'GET'): 'get_user',
-    ('/bank_accounts', 'POST'): 'create_bank_account',
-    ('/bank_accounts', 'GET') : 'get_bank_account',
-    ('/bank_accounts/transfer', 'POST') : 'transfer'
-}
-
 # Middleware for token auth
 @app.before_request
 def auth_token():
@@ -60,73 +53,46 @@ def auth_token():
     request_key = (request_path, request_method)
     logging.debug(f"Request key: {request_key}")
     logging.debug(f"Open routes: {open_routes}")
-    logging.debug(f"Closed routes: {closed_routes}")
-    logging.debug(f"All registered routes: {[str(rule) for rule in app.url_map.iter_rules()]}")
-    
+
     if request_key in open_routes:
         logging.debug(f"Matched open route: {request_key}")
         return
-    elif request_key in closed_routes:
-        logging.debug(f"Matched closed route: {request_key}")
+    
+    route_exists = any(rule.rule == request_path and rule.methods.intersection({request_method}) for rule in app.url_map.iter_rules())
+
+    if route_exists:  # Check if the route is valid
+        logging.debug(f"Route requires authorization: {request_key}")
         auth_header = request.headers.get('Authorization')
         
         if not auth_header:
-            return jsonify({'error' : 'Authorization header is missing.'}), 401
+            return jsonify({'error': 'Authorization header is missing.'}), 401
         
         auth_parts = auth_header.split()
         
-        
         if len(auth_parts) < 2:
-            return jsonify({'error': 'Invalid authorizaion header format.'}), 401
+            return jsonify({'error': 'Invalid authorization header format.'}), 401
         
         scheme = auth_parts[0].lower()
         
         if scheme == 'bearer':
-            # Assuming after the bearer the token should appear
             token = auth_parts[1]
-            
             try:
                 current_user_id = verify_token(token)
                 if not current_user_id:
                     raise TokenVerificationError
-                    return jsonify({'error': 'Invalid token'}), 401
                 
-                g.current_user_id = current_user_id # setting the current user id to the global variable
+                g.current_user_id = current_user_id  # setting the current user id to the global variable
                 
-            except Exception as e:
-                return jsonify({'error': 'General Internal Error'}), 500
-            
             except TokenVerificationError as e:
                 logging.debug(f"Failed to authenticate the token of user {current_user_id}", exc_info=True)
                 return jsonify({'error': 'Authentication failed'}), 401
             
-        # incase there are more than two parts to the auth_parts
-        # After meeting this part of code is redudant 
-        # Delete after this part after inserting role as part of the token 
-        # For later use
-        elif scheme == 'token':
-            token = auth_parts[1]
-            role = auth_parts[2] if len(auth_parts) > 2 else None
-            
-            try:
-                current_user_id = verify_token(token)
-                
-                if not current_user_id:
-                    raise TokenVerificationError
-                    return jsonify({'error': 'Invalid token'}), 401
-                g.current_user_id = current_user_id
-                g.role = role
-            except TokenVerificationError as e:
-                logging.debug(f"Failed to authenticate the token of user {current_user_id}", exc_info=True)
-                return jsonify({'error': 'Authentication failed'}), 401   
         else:
             logging.debug(f"Invalid authorization scheme {scheme}")
             return jsonify({'error': 'Unsupported authorization scheme.'})    
-
-    else: # route not in open/close routes
+    else:  # Route not found
         logging.debug(f"Route not found: {request_key}")
         return jsonify({'error': 'Not Found'}), 404
-                
 
 def check_database_connection():
     cursor, conn = get_db_connection()
diff --git a/test_app.py b/test_app.py
index 5b8feec..2a26cf0 100644
--- a/test_app.py
+++ b/test_app.py
@@ -7,6 +7,7 @@
 import json
 from datetime import datetime
 from dotenv import load_dotenv
+from faker import Faker
 
 load_dotenv()
 
@@ -17,6 +18,7 @@ def setUp(self):
         self.app.register_blueprint(bank_accounts_bp)
         self.app.register_blueprint(users_bp)
         self.client = self.app.test_client()
+        self.fake = Faker()
 
     # Bank Account Tests
 
@@ -28,24 +30,24 @@ def test_get_bank_accounts(self, mock_get_db_connection):
 
         mock_cursor.fetchall.return_value = [
             {
-                'id': '123',
-                'user_id': '456',
-                'account_number': '789',
-                'balance': 1000,
-                'type': 'CHECKINGS',
+                'id': self.fake.uuid4(),
+                'user_id': self.fake.uuid4(),
+                'account_number': self.fake.bban(),
+                'balance': self.fake.random_int(min=100, max=10000),
+                'type': self.fake.random_element(elements=('CHECKINGS', 'SAVINGS')),
                 'currency': 'USD',
-                'created_at': '2023-01-01',
+                'created_at': self.fake.date_this_decade().isoformat(),
                 'status': 'ACTIVE'
             }
         ]
 
-        response = self.client.get('/bank_accounts?user_id=456')
+        response = self.client.get(f'/bank_accounts?user_id={mock_cursor.fetchall()[0]["user_id"]}')
 
         self.assertEqual(response.status_code, 200)
         data = json.loads(response.data)
         self.assertEqual(len(data), 1)
-        self.assertEqual(data[0]['id'], '123')
-        self.assertEqual(data[0]['user_id'], '456')
+        self.assertEqual(data[0]['id'], mock_cursor.fetchall()[0]['id'])
+        self.assertEqual(data[0]['user_id'], mock_cursor.fetchall()[0]['user_id'])
 
     @patch('bank_accounts.get_db_connection')
     @patch('bank_accounts.check_is_valid_user_id')
@@ -56,18 +58,21 @@ def test_create_bank_account(self, mock_encrypt, mock_uuid4, mock_check_user, mo
         mock_conn = MagicMock()
         mock_get_db_connection.return_value = (mock_cursor, mock_conn)
 
-        mock_check_user.return_value = '456'
-        mock_uuid4.return_value.int = 123456
-        mock_uuid4.return_value = '123'
+        mock_check_user.return_value = self.fake.uuid4()
+        mock_uuid4.return_value = self.fake.uuid4()
         mock_encrypt.return_value = b'encrypted_account_number'
 
         response = self.client.post('/bank_accounts', 
-                                    data=json.dumps({'user_id': '456'}),
+                                    data=json.dumps({
+                                        'user_id': mock_check_user.return_value,
+                                        'currency': 'USD',
+                                        'account_type': self.fake.random_element(elements=('CHECKINGS', 'SAVINGS'))
+                                    }),
                                     content_type='application/json')
 
         self.assertEqual(response.status_code, 201)
         data = json.loads(response.data)
-        self.assertEqual(data['account_id'], '123')
+        self.assertEqual(data['account_id'], mock_uuid4.return_value)
         self.assertEqual(data['account_number'], 'encrypted_account_number')
 
     # User Tests
@@ -80,50 +85,56 @@ def test_register_user(self, mock_uuid4, mock_hash, mock_get_db_connection):
         mock_conn = MagicMock()
         mock_get_db_connection.return_value = (mock_cursor, mock_conn)
 
-        mock_uuid4.return_value = '123'
+        mock_uuid4.return_value = self.fake.uuid4()
         mock_hash.return_value = b'hashed_password'
 
+        # Store generated values
+        first_name = self.fake.first_name()
+        last_name = self.fake.last_name()
+        email = self.fake.email()
+        password = self.fake.password()
+
         response = self.client.post('/users/register', 
                                     data=json.dumps({
-                                        'first_name': 'John',
-                                        'last_name': 'Doe',
-                                        'email': 'john@example.com',
-                                        'password': 'password123'
+                                        'first_name': first_name,
+                                        'last_name': last_name,
+                                        'email': email,
+                                        'password': password
                                     }),
                                     content_type='application/json')
 
         self.assertEqual(response.status_code, 201)
         data = json.loads(response.data)
-        self.assertEqual(data['id'], '123')
-        self.assertEqual(data['first_name'], 'John')
-        self.assertEqual(data['last_name'], 'Doe')
-        self.assertEqual(data['email'], 'john@example.com')
+        self.assertEqual(data['id'], mock_uuid4.return_value)
+        self.assertEqual(data['first_name'], first_name)  # Use stored first name
+        self.assertEqual(data['last_name'], last_name)    # Use stored last name
+        self.assertEqual(data['email'], email)            # Use stored email
 
     @patch('users.get_user_by_email')
     def test_get_user(self, mock_get_user):
         mock_get_user.return_value = {
-            'id': '123',
-            'first_name': 'John',
-            'last_name': 'Doe',
-            'email': 'john@example.com'
+            'id': self.fake.uuid4(),
+            'first_name': self.fake.first_name(),
+            'last_name': self.fake.last_name(),
+            'email': self.fake.email()
         }
 
-        response = self.client.get('/users?email=john@example.com')
+        response = self.client.get(f'/users?email={mock_get_user.return_value["email"]}')
 
         self.assertEqual(response.status_code, 200)
         data = json.loads(response.data)
-        self.assertEqual(data['id'], '123')
-        self.assertEqual(data['first_name'], 'John')
-        self.assertEqual(data['last_name'], 'Doe')
-        self.assertEqual(data['email'], 'john@example.com')
+        self.assertEqual(data['id'], mock_get_user.return_value['id'])
+        self.assertEqual(data['first_name'], mock_get_user.return_value['first_name'])
+        self.assertEqual(data['last_name'], mock_get_user.return_value['last_name'])
+        self.assertEqual(data['email'], mock_get_user.return_value['email'])
 
     @patch('users.get_user_by_email')
     @patch('users.bcrypt.check_password_hash')
     @patch('users.generate_token')
     def test_login_user(self, mock_generate_token, mock_check_password, mock_get_user):
         mock_get_user.return_value = {
-            'id': '123',
-            'email': 'john@example.com',
+            'id': self.fake.uuid4(),
+            'email': self.fake.email(),
             'password_hash': 'hashed_password'
         }
         mock_check_password.return_value = True
@@ -131,7 +142,7 @@ def test_login_user(self, mock_generate_token, mock_check_password, mock_get_use
 
         response = self.client.post('/users/login',
                                     data=json.dumps({
-                                        'email': 'john@example.com',
+                                        'email': mock_get_user.return_value['email'],
                                         'password': 'password123'
                                     }),
                                     content_type='application/json')
@@ -139,14 +150,14 @@ def test_login_user(self, mock_generate_token, mock_check_password, mock_get_use
         self.assertEqual(response.status_code, 200)
         data = json.loads(response.data)
         self.assertEqual(data['message'], 'Logged In Successfully')
-        self.assertEqual(data['toekn'], 'fake_token')  # Note: There's a typo in your original code ('toekn' instead of 'token')
+        self.assertEqual(data['token'], 'fake_token')
 
     def test_register_user_missing_field(self):
         response = self.client.post('/users/register', 
                                     data=json.dumps({
-                                        'first_name': 'John',
-                                        'last_name': 'Doe',
-                                        'email': 'john@example.com'
+                                        'first_name': self.fake.first_name(),
+                                        'last_name': self.fake.last_name(),
+                                        'email': self.fake.email()
                                         # Missing password field
                                     }),
                                     content_type='application/json')
@@ -161,7 +172,7 @@ def test_login_user_invalid_credentials(self, mock_get_user):
 
         response = self.client.post('/users/login',
                                     data=json.dumps({
-                                        'email': 'nonexistent@example.com',
+                                        'email': self.fake.email(),
                                         'password': 'wrongpassword'
                                     }),
                                     content_type='application/json')
@@ -173,9 +184,9 @@ def test_login_user_invalid_credentials(self, mock_get_user):
     @patch('users.get_user_by_email')
     def test_get_user_not_found(self, mock_get_user):
         # Provide the required argument for UserNotFoundError
-        mock_get_user.side_effect = UserNotFoundError(email='nonexistent@example.com')
+        mock_get_user.side_effect = UserNotFoundError(email=self.fake.email())
 
-        response = self.client.get('/users?email=nonexistent@example.com')
+        response = self.client.get(f'/users?email={mock_get_user.side_effect.email}')
 
         self.assertEqual(response.status_code, 404)
         data = json.loads(response.data)
diff --git a/users.py b/users.py
index 98268ce..3406bec 100644
--- a/users.py
+++ b/users.py
@@ -48,24 +48,26 @@ def register():
 
     # Connect to the database and insert new user (handle database exceptions)
     try:
-        # psycopg2 sql injection
         cursor, conn = get_db_connection()
-        
+        cursor.execute("BEGIN;")
         cursor.execute("""
             INSERT INTO users (id, first_name, last_name, email, password_hash, created_at, updated_at) 
             VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;
         """, (user_id, data['first_name'], data['last_name'], email, hashed_password, str(created_at), str(updated_at)))
+        conn.commit()
         cursor.close()
         conn.close()
 
+
+    except UniqueViolation as UV:
+        logging.debug(f"Unique constraint violation for email {email}: {UV}")
+        conn.rollback()
+        return jsonify({'error': 'Try another email.'}), 409
+    
     except Exception as e:
         logging.debug("Error creating user: ", exc_info=True)
         return jsonify({'error': 'Internal Error'}), 500
     
-    except UniqueViolation as UV:
-        logging.debug(f"Unique constraint violation for email {email}: {UV}")
-        return jsonify({'error': 'Try another email.'}), 409
-
     # Return the created user data
     return jsonify({'id': user_id, 'first_name': data['first_name'], 'last_name': data['last_name'], 'email': data['email']}), 201