├── src ├── __init__.py └── long_banking_system │ ├── __init__.py │ ├── interface.py │ ├── admin.py │ ├── main.py │ ├── admins_data.json │ ├── consts.py │ ├── models.py │ ├── messages.py │ ├── sorts.py │ ├── transactions.py │ ├── utils.py │ ├── account.py │ └── services.py ├── requirements.txt ├── pyproject.toml ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── .gitignore ├── README.md └── tests └── test_utils.py /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/long_banking_system/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bcrypt==3.2.2 2 | maskpass==0.3.6 -------------------------------------------------------------------------------- /src/long_banking_system/interface.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | 5 | def clean_terminal_screen(): 6 | """ 7 | Cleans the terminal screen by performing a system 8 | clear command. Cls on windows and Clear on UNIX ones. 9 | """ 10 | 11 | os.system("cls" if os.name == "nt" else "clear") 12 | time.sleep(0.2) 13 | 14 | def display_horizontal_line(): 15 | """ 16 | A pretty decorative horizontal line. 17 | """ 18 | 19 | print("───────────────────────────────────────────────────────────────") 20 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "long_banking_system" 3 | version = "0.0.1" 4 | authors = [ 5 | { name="Long_Chau_Do", email="dolong2110@gmail.com" }, 6 | ] 7 | description = "Simple Online Banking System" 8 | readme = "README.md" 9 | requires-python = ">=3.8" 10 | classifiers = [ 11 | "Programming Language :: Python :: 3", 12 | "License :: OSI Approved :: MIT License", 13 | "Operating System :: OS Independent", 14 | ] 15 | 16 | [project.urls] 17 | Homepage = "https://github.com/dolong2110/long-banking-system" 18 | Issues = "https://github.com/dolong2110/long-banking-system/issues" 19 | -------------------------------------------------------------------------------- /src/long_banking_system/admin.py: -------------------------------------------------------------------------------- 1 | import account 2 | import messages 3 | import models 4 | import services 5 | 6 | if __name__ == '__main__': 7 | admin_data, user_index = account.authentication("admins") 8 | feedbacks_messages = None 9 | if admin_data and user_index != -1: 10 | feedbacks_messages = messages.MessageQueue() 11 | 12 | users = models.Users() 13 | while admin_data: 14 | admin_data, feedbacks_messages = services.admins_services(admin_data, users, user_index, feedbacks_messages) 15 | 16 | feedbacks_messages.update() 17 | 18 | print("Goodbye, see you soon!!!") -------------------------------------------------------------------------------- /src/long_banking_system/main.py: -------------------------------------------------------------------------------- 1 | import account 2 | import messages 3 | import services 4 | 5 | if __name__ == '__main__': 6 | users_data, user_index = account.authentication("users") 7 | feedbacks_messages = None 8 | if users_data and user_index != -1: 9 | print("Hello world") 10 | feedbacks_messages = messages.MessageQueue() 11 | 12 | while users_data: 13 | users_data, feedbacks_messages = services.users_services(users_data, user_index, feedbacks_messages) 14 | 15 | if feedbacks_messages: 16 | feedbacks_messages.update() 17 | 18 | print("Thank you so much for using Long bank's app, see you soon!!!") 19 | -------------------------------------------------------------------------------- /src/long_banking_system/admins_data.json: -------------------------------------------------------------------------------- 1 | [{"first_name": "Long", "middle_name": "Chau", "last_name": "Do", "gender": "male", "date_of_birth": "21/10/1998", "phone_number": "0975672596", "email": "dolong2110@gmail.com", "password": "$2b$12$V3VEajXIAbGMCqUk5KO/guCob6cmLpD6JH/NK/96uZlpNHw07ptsa", "balance": "0", "account_number": "40109", "issued_date": "05/08/2022"}, {"first_name": "Long", "middle_name": "Chau", "last_name": "Do", "gender": "male", "date_of_birth": "21/10/1998", "phone_number": "0975672596", "email": "dolong2110@gmail.com", "password": "$2b$12$m7x1YYZ3xmTcuDDDLVdc0OI20nZtGTIKeMJEzfY0spBHN3eg0YpBa", "balance": "0", "account_number": "46390", "issued_date": "05/08/2022"}, {"first_name": "Long", "middle_name": "Chau", "last_name": "Do", "gender": "male", "date_of_birth": "21/10/1998", "phone_number": "0975672596", "email": "dolong2110@gmail.com", "password": "$2b$12$1wuoISX4t2u4yvyINpDPYOEi6NR0RkBkbUz28XpFuNJW4GPoTo4Du", "balance": "0", "account_number": "66484", "issued_date": "05/08/2022"}] -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | 8 | jobs: 9 | continuous-integration: 10 | name: Continuous-Integration 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: 3.x 19 | - uses: actions/cache@v3 20 | with: 21 | path: ~/.cache/pip 22 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip setuptools six wheel 26 | python -m pip install pytest-cov -r requirements.txt 27 | - name: Run tests 28 | run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/validate_solutions.py --cov-report=term-missing:skip-covered --cov=. . 29 | - if: ${{ success() }} 30 | run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chau Long Do 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/long_banking_system/consts.py: -------------------------------------------------------------------------------- 1 | BANK_PREFIX = "21101998" 2 | ACCOUNT_CONFIGS = {"users": {"file_name": "users_data.json", "account_number_len": 10}, 3 | "admins": {"file_name": "admins_data.json", "account_number_len": 5}} 4 | MESSAGES_DATA_PATH = "messages_data.json" 5 | FAILED_ATTEMPT = 5 6 | AUTHENTICATION_CHOICES = {"1", "2", "3"} 7 | USER_SERVICES_CHOICES = {"1", "2", "3", "4", "5", "6", "7"} 8 | ADMIN_SERVICES_CHOICES = {"1", "2", "3", "4", "5", "6", "7", "8"} 9 | USER_UPDATE_INFORMATION_CHOICES = {"1", "2", "3", "4", "5", "6", "7", "8"} 10 | USER_TRANSACTION_CHOICES = {"1", "2", "3", "4"} 11 | YES_NO_CHOICES = {"1", "2"} 12 | GENDER_SET_CHOICE = {"1": "male", "2": "female", "3": "others"} 13 | SALT_LEN = 12 14 | FIRST_NAME_MAX_LEN = 256 15 | MIDDLE_NAME_MAX_LEN = 128 16 | LAST_NAME_MAX_LEN = 128 17 | PHONE_NUMBER_LEN = 10 18 | MIN_PASSWORD_LEN = 8 19 | MAX_PASSWORD_LEN = 100 20 | MESSAGE_MAX_CHARACTERS = 1000 21 | MESSAGE_MAX_WORDS = 150 22 | 23 | 24 | 25 | 26 | # repeated strings 27 | FILE_NAME = "file_name" 28 | ACCOUNT_NUMBER_LEN = "account_number_len" 29 | FIRST_NAME = "first_name" 30 | MIDDLE_NAME = "middle_name" 31 | LAST_NAME = "last_name" 32 | GENDER = "gender" 33 | DATE_OF_BIRTH = "date_of_birth" 34 | PHONE_NUMBER = "phone_number" 35 | EMAIL = "email" 36 | PASSWORD = "password" 37 | BALANCE = "balance" 38 | ACCOUNT_NUMBER = "account_number" 39 | ISSUED_DATE = "issued_date" 40 | MESSAGE = "message" 41 | TIMESTAMP = "timestamp" 42 | TIME = "time" 43 | 44 | # ADMIN 45 | FIELDS_SEARCH = {"1": ACCOUNT_NUMBER, "2": BALANCE, "3": FIRST_NAME} -------------------------------------------------------------------------------- /src/long_banking_system/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | from collections import defaultdict 4 | 5 | 6 | import sorts 7 | from consts import * 8 | import utils 9 | 10 | 11 | class Users: 12 | 13 | def __init__(self, privilege: str = "users"): 14 | 15 | self.privilege = privilege 16 | self.configs = ACCOUNT_CONFIGS[privilege] 17 | raw_data = utils.get_data_from_json(self.configs[FILE_NAME]) 18 | sorting = sorts.Sorting(raw_data, ACCOUNT_NUMBER, "") 19 | self.data = sorting.arr if sorting.arr else [] 20 | self.users_set = set([account[ACCOUNT_NUMBER] for account in self.data]) 21 | 22 | def create_new(self, user_information: defaultdict[str]) -> int: 23 | """ 24 | Create new user with the given information 25 | """ 26 | 27 | new_account_number = self.generate_account_number(self.configs[ACCOUNT_NUMBER_LEN]) 28 | while new_account_number in self.users_set: 29 | new_account_number = self.generate_account_number(self.configs[ACCOUNT_NUMBER_LEN]) 30 | 31 | today_date = datetime.date.today().strftime("%d/%m/%Y") 32 | user_information[ACCOUNT_NUMBER] = new_account_number 33 | user_information[ISSUED_DATE] = today_date 34 | self.data.append(user_information) 35 | self.users_set.add(new_account_number) 36 | utils.write_data_to_json(self.data, self.configs[FILE_NAME]) 37 | 38 | return len(self.data) - 1 39 | 40 | 41 | def delete_user(self, index: int) -> None: 42 | """ 43 | Delete user that delete it from database and current data structure 44 | """ 45 | 46 | account_number = self.data[index][ACCOUNT_NUMBER] 47 | self.data = self.data[:index] + self.data[index + 1:] 48 | self.users_set.remove(account_number) 49 | utils.write_data_to_json(self.data, self.configs[FILE_NAME]) 50 | 51 | 52 | @staticmethod 53 | def generate_account_number(account_len: int): 54 | """ 55 | Generates a new unique account number 56 | It includes first 8 numbers of the bank and 8 random numbers 57 | """ 58 | 59 | new_number = [] 60 | for _ in range(account_len): 61 | new_number.append(str(random.randint(0, 9))) 62 | 63 | return ''.join(new_number) 64 | 65 | 66 | def update_information(self, index: int, field: str, change: str): 67 | """ 68 | Changes the information of user. 69 | """ 70 | 71 | self.data[index][field] = change 72 | utils.write_data_to_json(self.data, self.configs[FILE_NAME]) 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # folders, files in project 132 | .idea 133 | /bank-system-env 134 | users_data.json 135 | messages_data.json 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🏦 Long Banking System 🏦

2 |

using Python 🐍


3 |

4 | Star Badge 5 | 6 | GitHub 7 | 8 |

9 |
10 | 11 | ## 📃 Table of Contents: 12 | - [About Project](#-about-project) 13 | - [System Design](#-system-design) 14 | - [Screenshot](#-screenshot) 15 | - [Install](#-install) 16 | 17 |
18 | 19 | ## 🖋 About Project: 20 | 👉 An online banking system built using Python. This system allows the customer - user privilege, and banking staff/manager - admin privilege, to manage all transactions easily.
21 | 👉 It performs basic services of an online banking system: authentication, transaction, etc.
22 | 👉 It practices with many algorithm and data structures: queue, double linked-list, various sorting algorithms.
23 | 👉 As mentioned above, there are 2 users for this system: 24 | 1. **Admin**: 25 | - Can **do basic authentication** such as login, create, delete 26 | - Can **view and search customer profile** 27 | - Can **read customers' feedback** 28 | 2. **User**: 29 | - Can **do basic authentication** such as login, create, delete 30 | - Can **do the transactions** 31 | - Can **send the feedbacks** 32 | 33 | > Note: **Every user needs to create account and sign in to access the system. All the data must be stored in json (.json) files.**
34 | 35 | [![](https://img.shields.io/badge/back%20to%20top-%E2%86%A9-red)](#-table-of-contents) 36 |

37 | 38 | ## 💻 System Design: 39 | 👉 In this online banking system, there are 11 different menus, namely: 40 | 1. **Main Menu** 41 | 2. **Admin** 42 | - Admin Login 43 | - Admin Menu 44 | - Admin New Profile 45 | - Admin Search Customer Data 46 | - Admin Search Customer Transaction 47 | 3. **Customer** 48 | - Customer Login 49 | - Customer Menu 50 | - Customer Deposit 51 | - Customer Withdraw 52 | - Customer View 53 |

54 | [![](https://img.shields.io/badge/back%20to%20top-%E2%86%A9-red)](#-table-of-contents) 55 |

56 | 57 | ## 📷 Screenshot: 58 | 59 |

60 | [![](https://img.shields.io/badge/back%20to%20top-%E2%86%A9-red)](#-table-of-contents) 61 |

62 | 63 | ## Install: 64 | - First clone the repo 65 | 66 | ``` 67 | git clone https://github.com/dolong2110/long-banking-system.git 68 | ``` 69 | 70 | - Install pip 71 | 72 | ``` 73 | py -m pip install --upgrade pip 74 | py -m pip --version 75 | ``` 76 | 77 | - Install virtualenv 78 | 79 | ``` 80 | py -m pip install --user virtualenv 81 | ``` 82 | 83 | - Move current directory to the project directory 84 | 85 | ``` 86 | cd ..\long-banking-system 87 | ``` 88 | 89 | - Create a virtual environment 90 | 91 | ``` 92 | py -m venv bank-system-env 93 | ``` 94 | 95 | - Activating the virtual environment 96 | 97 | ``` 98 | .\env\Scripts\activate 99 | ``` 100 | 101 | - Install all dependencies 102 | 103 | ``` 104 | pip install -r requirements.txt 105 | ``` 106 | 107 | - Run as an user 108 | 109 | ``` 110 | py main.py 111 | ``` 112 | 113 | - Run as an admin 114 | 115 | ``` 116 | py admin.py 117 | ``` 118 | 119 |

120 | [![](https://img.shields.io/badge/back%20to%20top-%E2%86%A9-red)](#-table-of-contents) 121 |

122 | 123 | ## 🙌 Support me! 124 | 125 | 👉 If you find this project useful, **please ⭐ this repository 😆**! 126 | 127 | [![](https://img.shields.io/badge/back%20to%20top-%E2%86%A9-red)](#-table-of-contents) 128 |
-------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from src.long_banking_system.utils import * 4 | 5 | class Test(TestCase): 6 | 7 | # def test_greeting(self): 8 | # self.fail() 9 | # 10 | # 11 | # def test_generate_hashed_password(self): 12 | # self.fail() 13 | # 14 | # 15 | # def test_check_password(self): 16 | # self.fail() 17 | 18 | 19 | def test_is_valid_name(self): 20 | self.countTestCases() 21 | self.assertTrue(is_valid_name("long", 4)) 22 | self.assertTrue(is_valid_name("도주용", 4)) 23 | self.assertFalse(is_valid_name("long", 3)) # exceed length 24 | self.assertFalse(is_valid_name("long%", 6)) # contain special character 25 | self.assertFalse(is_valid_name("long#", 6)) # contain special character 26 | self.assertFalse(is_valid_name("""lon"g""", 6)) # contain special character 27 | self.assertFalse(is_valid_name("!long", 6)) # contain special character 28 | self.assertFalse(is_valid_name("long'", 6)) # contain special character 29 | self.assertFalse(is_valid_name("long1", 6)) # contain number 30 | 31 | 32 | # def test_get_temporal(self): 33 | # self.fail() 34 | # 35 | # 36 | 37 | 38 | def test_is_valid_day(self): 39 | self.assertTrue(is_valid_day([26, 10, 2003], [4, 8, 2022])) 40 | self.assertTrue(is_valid_day([18, 8, 1988], [4, 8, 2022])) 41 | self.assertFalse(is_valid_day([30, 2, 2018], [4, 8, 2022])) # invalid day in that month 42 | self.assertFalse(is_valid_day([31, 9, 2019], [4, 8, 2022])) # invalid day in that month 43 | self.assertFalse(is_valid_day([0, 12, 2010], [4, 8, 2022])) # invalid day in that month 44 | self.assertFalse(is_valid_day([40, 5, 1967], [4, 8, 2022])) # invalid day in that month 45 | self.assertFalse(is_valid_day([11, 15, 2008], [4, 8, 2022])) # invalid month 46 | self.assertFalse(is_valid_day([3, 30, 2004], [4, 8, 2022])) # invalid month 47 | self.assertFalse(is_valid_day([8, 0, 2009], [4, 8, 2022])) # invalid month 48 | 49 | 50 | # def test_is_valid_phone_number(self): 51 | # self.fail() 52 | # 53 | # 54 | # def test_is_valid_email(self): 55 | # self.fail() 56 | # 57 | 58 | 59 | def test_is_valid_password(self): 60 | self.assertTrue(is_valid_password("21101998Abc.")) 61 | self.assertTrue(is_valid_password("26102003@Abcd")) 62 | self.assertTrue(is_valid_password("aA^U@UgVVE~An%eMxnzN#SAJ^^hG=p3CRuT875B`o6=%c36n*@=JW7c9mH~_SPPa%9" 63 | "nQAAvnLAvfPCjThG`QhtsG#gS=Wymkg#~q")) 64 | self.assertFalse(is_valid_password("21101998Abc")) # not have special characters 65 | self.assertFalse(is_valid_password("21101998abc.")) # not have upper case letters 66 | self.assertFalse(is_valid_password("26102003_abcd")) # not have upper case letters 67 | self.assertFalse(is_valid_password("abcd1234")) # not have special characters and upper case letters 68 | self.assertFalse(is_valid_password("dochautuan@gmail.com")) # not have upper letters 69 | self.assertFalse(is_valid_password("OPTIMIZATION@1234")) # not have lower letters 70 | self.assertFalse(is_valid_password("tuandz")) # not have enough characters 71 | self.assertFalse(is_valid_password("21101998Abc. ")) # not have space character 72 | self.assertFalse(is_valid_password("_D3ns3J+c/3rD=FJ/$#/-hh~y4svT$8Kuner-aF&KD23ZXLrL#w-%aVKwc'fiz5kM7i=" 73 | "FRaHFyYB*6/aw8-rTda$RtWwxbUnwq`+tuan")) # having more than 100 characters 74 | 75 | 76 | # def test_is_valid_account_number(self): 77 | # self.fail() 78 | # 79 | # 80 | # def test_get_yes_no_choice(self): 81 | # self.fail() 82 | # 83 | # 84 | # def test_is_valid_balance(self): 85 | # self.fail() 86 | # 87 | # 88 | # def test_is_valid_message(self): 89 | # self.fail() 90 | # 91 | # 92 | # def test_proceed_next(self): 93 | # self.fail() 94 | -------------------------------------------------------------------------------- /src/long_banking_system/messages.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from collections import defaultdict 3 | from typing import Optional 4 | 5 | from consts import * 6 | import utils 7 | 8 | 9 | class Node: 10 | 11 | def __init__(self, data: defaultdict[str], next = None, prev = None): 12 | self.data = data 13 | self.next = next 14 | self.prev = prev 15 | 16 | 17 | class MessageQueue: 18 | 19 | def __init__(self): 20 | head_data, tail_data = defaultdict(str), defaultdict(str) 21 | head_data["dummy"], tail_data["dummy"] = "head", "tail" 22 | self.head, self.tail = Node(head_data), Node(tail_data) 23 | self.head.next, self.tail.prev = self.tail, self.head 24 | self.size = 0 25 | self.make_queue() 26 | 27 | def make_queue(self): 28 | for data in utils.get_data_from_json(MESSAGES_DATA_PATH): 29 | node = Node(data) 30 | self._add(node) 31 | 32 | def get_front(self) -> Optional[Node]: 33 | if not self.size: 34 | return None 35 | 36 | return self.head.next 37 | 38 | def get_last(self) -> Optional[Node]: 39 | if not self.size: 40 | return None 41 | 42 | return self.tail.prev 43 | 44 | def enqueue(self, data: defaultdict[str]) -> None: 45 | self._add(Node(data)) 46 | 47 | def dequeue(self) -> None: 48 | if not self.size: 49 | return None 50 | 51 | self._remove(self.head.next) 52 | 53 | def update(self) -> None: 54 | data_list = [] 55 | # data_list = [data for data in utils.get_data_from_json(MESSAGES_DATA_PATH)] 56 | cur = self.head.next 57 | while cur != self.tail: 58 | data_list.append(cur.data) 59 | cur = cur.next 60 | 61 | utils.write_data_to_json(data_list, MESSAGES_DATA_PATH) 62 | 63 | def _add(self, node: Node) -> None: 64 | prev_node = self.tail.prev 65 | node.next, node.prev = self.tail, prev_node 66 | self.tail.prev, prev_node.next = node, node 67 | self.size += 1 68 | 69 | def _remove(self, node: Node): 70 | prev, nxt = node.prev, node.next 71 | prev.next, nxt.prev = nxt, prev 72 | self.size -= 1 73 | 74 | 75 | 76 | def add_message(messages: MessageQueue, account_number: str) -> MessageQueue: 77 | print("We are very appreciated to hear from your thinking, your satisfied, dis-satisfied, " 78 | "and the idea to help us improve!!!") 79 | print(f"Your message should be less than {MESSAGE_MAX_CHARACTERS} characters and " 80 | f"less than {MESSAGE_MAX_WORDS} words") 81 | failed_attempt = FAILED_ATTEMPT 82 | message = "" 83 | while failed_attempt: 84 | message = input("Please give a message here: ") 85 | if not utils.is_valid_message(message): 86 | failed_attempt -= 1 87 | print("Your message is too long, please try a shorter one!!!") 88 | print("You have %d try left!!!" % failed_attempt) 89 | else: 90 | break 91 | 92 | if not failed_attempt: 93 | print("You send invalid message many times, please wait a few minutes to try it again!!!") 94 | return messages 95 | 96 | time_now = datetime.now() 97 | timestamp = datetime.timestamp(time_now) 98 | message_data = defaultdict(str) 99 | message_data[ACCOUNT_NUMBER] = account_number 100 | message_data[MESSAGE] = message 101 | message_data[TIMESTAMP] = str(timestamp) 102 | message_data[TIME] = str(time_now) 103 | print(message_data) 104 | 105 | messages.enqueue(message_data) 106 | 107 | return messages 108 | 109 | def read_message(messages: MessageQueue) -> Optional[MessageQueue]: 110 | print("Let's see the users' feedback to improve our application") 111 | if not messages.size: 112 | print("There are no messages to read") 113 | utils.proceed_next() 114 | return messages 115 | 116 | message_data = messages.get_front().data 117 | 118 | messages.dequeue() 119 | print(f"Messages from user: {message_data[ACCOUNT_NUMBER]}") 120 | utils.proceed_next() 121 | print(f"Time message sent: {message_data[TIME]}") 122 | utils.proceed_next() 123 | print(f"Message: {message_data[MESSAGE]}") 124 | utils.proceed_next() 125 | print(message_data) 126 | utils.proceed_next() 127 | 128 | return messages -------------------------------------------------------------------------------- /src/long_banking_system/sorts.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | class Sorting: 5 | 6 | def __init__(self, arr, field: str, method: str = ""): 7 | self.arr = arr 8 | self.field = field 9 | self.len = len(arr) 10 | 11 | if self.len < 20 or method == "insertion_sort": 12 | self.insertion_sort() 13 | return 14 | 15 | if method == "bubble": 16 | self.bubble_sort() 17 | return 18 | 19 | if self.len > 10**7 or method == "counting_sort": 20 | self.counting_sort() 21 | return 22 | 23 | self.heap_sort() 24 | 25 | def selection_sort(self) -> None: 26 | """ 27 | Mutates lst so that it is sorted via selecting the minimum element and 28 | swapping it with the corresponding index 29 | Time complexity: O(n^2); Space complexity: O(1) 30 | Naive and not stable sorting 31 | """ 32 | for i in range(self.len): 33 | min_index = i 34 | for j in range(i + 1, self.len): 35 | # Update minimum index 36 | if self.arr[j][self.field] < self.arr[min_index][self.field]: 37 | min_index = j 38 | 39 | # Swap current index with minimum element in rest of list 40 | self.arr[min_index], self.arr[i] = self.arr[i], self.arr[min_index] 41 | 42 | def bubble_sort(self) -> None: 43 | """ 44 | Mutates lst so that it is sorted via swapping adjacent elements until 45 | the entire lst is sorted. 46 | Time complexity: O(n^2); Space complexity: O(1) 47 | Naive and stable sorting 48 | """ 49 | has_swapped = True 50 | # if no swap occurred, lst is sorted 51 | while has_swapped: 52 | has_swapped = False 53 | for i in range(self.len - 1): 54 | if self.arr[i][self.field] > self.arr[i + 1][self.field]: 55 | # Swap adjacent elements 56 | self.arr[i], self.arr[i + 1] = self.arr[i + 1], self.arr[i] 57 | has_swapped = True 58 | 59 | def insertion_sort(self) -> None: 60 | """ 61 | Mutates elements in a lst by inserting out of place elements into appropriate 62 | index repeatedly until lst is sorted 63 | Time complexity: O(n^2); Space complexity: O(1) 64 | Useful for small size array or nearly sorted array 65 | """ 66 | 67 | for i in range(1, self.len): 68 | current_index = i 69 | 70 | while current_index > 0 and self.arr[current_index][self.field] < self.arr[current_index - 1][self.field]: 71 | # Swap elements that are out of order 72 | self.arr[current_index], self.arr[current_index - 1] = \ 73 | self.arr[current_index - 1], self.arr[current_index] 74 | current_index -= 1 75 | 76 | def heap_sort(self) -> None: 77 | """ 78 | Mutates elements in lst by utilizing the heap data structure 79 | Time complexity: O(NlogN); Space complexity: O() 80 | A direct optimization of selection sort 81 | """ 82 | 83 | # Time Complexity: O(logN) 84 | def max_heapify(heap_size, index): 85 | left, right = 2 * index + 1, 2 * index + 2 86 | largest = index 87 | if left < heap_size and self.arr[left][self.field] > self.arr[largest][self.field]: 88 | largest = left 89 | if right < heap_size and self.arr[right][self.field] > self.arr[largest][self.field]: 90 | largest = right 91 | if largest != index: 92 | self.arr[index][self.field], self.arr[largest][self.field] = \ 93 | self.arr[largest][self.field], self.arr[index][self.field] 94 | max_heapify(heap_size, largest) 95 | 96 | # heapify original lst 97 | for i in range(self.len // 2 - 1, -1, -1): 98 | max_heapify(self.len, i) 99 | 100 | # Time Complexity: O(N) 101 | # use heap to sort elements 102 | for i in range(self.len - 1, 0, -1): 103 | # swap last element with first element 104 | self.arr[i], self.arr[0] = self.arr[0], self.arr[i] 105 | # note that we reduce the heap size by 1 every iteration 106 | max_heapify(i, 0) 107 | 108 | 109 | def counting_sort(self) -> None: 110 | """ 111 | Sorts a list of integers where minimum value is 0 and maximum value is k 112 | Time complexity: O(N+k) - N is size of input, k is the maximum number in arr; Space complexity: O(N) 113 | better when sorting numbers - id, user_index 114 | """ 115 | k = max(self.arr) 116 | counts = [0 for _ in range(k + 1)] 117 | for element in self.arr: 118 | counts[element] += 1 119 | 120 | # we now overwrite our original counts with the starting index 121 | # of each element in the final sorted array 122 | 123 | starting_index = 0 124 | for i, count in enumerate(counts): 125 | counts[i] = starting_index 126 | starting_index += count 127 | 128 | sorted_lst = [0 for _ in range(self.len)] 129 | 130 | for element in self.arr: 131 | sorted_lst[counts[element]] = element 132 | # since we have placed an item in index counts[element], we need to 133 | # increment counts[element] index by 1 so the next duplicate element 134 | # is placed in appropriate index 135 | counts[element] += 1 136 | 137 | # common practice to copy over sorted list into original lst 138 | # it's fine to just return the sorted_lst at this point as well 139 | for i in range(self.len): 140 | self.arr[i] = sorted_lst[i] 141 | 142 | def binary_search(arr: List[dict], field: str, query: str) -> int: 143 | """ 144 | A custom binary search implementation that: 145 | (1) Assumes the input_list to have elements of type object 146 | and then sorts by a common key in all those objects name 147 | "field" 148 | (2) Make the text lowercase and trims the text in the fields 149 | so for example "foo bar" can match "FooBar" 150 | """ 151 | 152 | low, high = 0, len(arr) - 1 153 | while low <= high: 154 | mid = (low + high) // 2 155 | if arr[mid][field] > query: 156 | high = mid - 1 157 | elif arr[mid][field] < query: 158 | low = mid + 1 159 | else: 160 | return mid 161 | 162 | return -1 163 | -------------------------------------------------------------------------------- /src/long_banking_system/transactions.py: -------------------------------------------------------------------------------- 1 | import sorts 2 | from consts import * 3 | import models 4 | import utils 5 | 6 | 7 | def transaction_services(users: models.Users, user_index: int) -> models.Users: 8 | 9 | print("Please choose which privilege of transaction you want!!!") 10 | print(" ┌─────────────┐ ╭───────────────────────────╮ ") 11 | print(" │ │ │ ▶︎ 1 • Check Balance │ ") 12 | print(" │ │ ├───────────────────────────┴╮ ") 13 | print(" │ │ │ ▶︎ 2 • Transfer Money │ ") 14 | print(" │ L O N G │ ├───────────────────────────┬╯ ") 15 | print(" │ T U A N │ │ ▶︎ 3 • Deposit Money │ ") 16 | print(" │ B A N K │ ├───────────────────────────┴─╮ ") 17 | print(" │ │ │ ▶︎ 4 • Withdraw Money │ ") 18 | print(" │ │ ├──────────────────┬──────────╯ ") 19 | print(" │ │ │ ▶︎ 5 • Exit │ ") 20 | print(" └─────────────┘ ╰──────────────────╯ ") 21 | 22 | failed_attempt = FAILED_ATTEMPT 23 | user_choice = "" 24 | while failed_attempt: 25 | user_choice = input("☞ Enter your choice: ") 26 | if user_choice not in USER_TRANSACTION_CHOICES: 27 | failed_attempt -= 1 28 | print("Wrong choice!!! Please choose only from 1 to 4") 29 | print("You have %d try left!!!" % failed_attempt) 30 | else: 31 | break 32 | 33 | if not failed_attempt: 34 | print("You enter wrong choice many times, please wait few minutes to do it again") 35 | return users 36 | 37 | if user_choice == "1": 38 | print(f"Your current account balance is: {users.data[user_index][BALANCE]}") 39 | utils.proceed_next() 40 | 41 | if user_choice == "2": 42 | users = transfer_money(users, user_index) 43 | 44 | if user_choice == "3": 45 | users = deposit_money(users, user_index) 46 | 47 | if user_choice == "4": 48 | users = withdraw_money(users, user_index) 49 | 50 | if user_choice == "5": 51 | print("Finish action!!!") 52 | return users 53 | 54 | return users 55 | 56 | def transfer_money(users: models.Users, user_index: int) -> models.Users: 57 | return _transfer_internal(users, user_index) 58 | 59 | def deposit_money(users, user_index: int) -> models.Users: 60 | deposit = _get_money(float('inf')) 61 | if not deposit: 62 | print("Failed to deposit money into bank") 63 | return users 64 | 65 | print(f"Do you want to proceed to transfer {deposit} into your bank account?") 66 | user_choice = utils.get_yes_no_choice() 67 | if not user_choice: 68 | print("Finish action!!!") 69 | return users 70 | 71 | if user_choice == "2": 72 | print("Finish action!!!") 73 | return users 74 | 75 | user = users.data[user_index] 76 | current_balance = user[BALANCE] 77 | expect_balance = float(current_balance) + float(deposit) 78 | print(f"After depositing your balance will be: {expect_balance}") 79 | 80 | print("Do you confirm to deposit?") 81 | user_choice = utils.get_yes_no_choice() 82 | if not user_choice: 83 | print("Finish action!!!") 84 | return users 85 | 86 | if user_choice == "2": 87 | print("Finish action!!!") 88 | return users 89 | 90 | users.update_information(user_index, BALANCE, str(expect_balance)) 91 | print("Successfully deposit the money!!!") 92 | print(f"Your balance now is: {expect_balance}") 93 | 94 | return users 95 | 96 | def withdraw_money(users: models.Users, user_index: int) -> models.Users: 97 | user = users.data[user_index] 98 | current_balance = float(user[BALANCE]) 99 | withdraw = _get_money(current_balance) 100 | if not withdraw: 101 | print("Failed to withdraw money from bank") 102 | return users 103 | 104 | print(f"Do you want to proceed to withdraw {withdraw} from your bank account?") 105 | user_choice = utils.get_yes_no_choice() 106 | if not user_choice: 107 | print("Finish action!!!") 108 | return users 109 | 110 | if user_choice == "2": 111 | print("Finish action!!!") 112 | return users 113 | 114 | expect_balance = float(current_balance) - float(withdraw) 115 | print(f"After withdrawing your balance will be: {expect_balance}") 116 | 117 | print("Do you confirm to withdraw?") 118 | user_choice = utils.get_yes_no_choice() 119 | if not user_choice: 120 | print("Finish action!!!") 121 | return users 122 | 123 | if user_choice == "2": 124 | print("Finish action!!!") 125 | return users 126 | 127 | users.update_information(user_index, BALANCE, str(expect_balance)) 128 | print("Successfully withdraw the money!!!") 129 | print(f"Your balance now is: {expect_balance}") 130 | 131 | return users 132 | 133 | def _transfer_internal(users: models.Users, user_index: int) -> models.Users: 134 | failed_attempt = FAILED_ATTEMPT 135 | receiver_account_number = "" 136 | account_number = users.data[user_index][ACCOUNT_NUMBER] 137 | while failed_attempt: 138 | receiver_account_number = input("☞ Enter the received account number: ") 139 | if not utils.is_valid_account_number(receiver_account_number, "users"): 140 | failed_attempt -= 1 141 | print("Invalid account number!!!") 142 | print("You have %d try left!!!" % failed_attempt) 143 | elif receiver_account_number == account_number: 144 | failed_attempt -= 1 145 | print("It is your own account, please try it again!!!") 146 | print("You have %d try left!!!" % failed_attempt) 147 | elif receiver_account_number not in users.users_set: 148 | failed_attempt -= 1 149 | print("Account does not exist!!!") 150 | print("You have %d try left!!!" % failed_attempt) 151 | else: 152 | break 153 | 154 | if not failed_attempt: 155 | print("You enter wrong account number many times, please wait few minutes to do it again") 156 | return users 157 | 158 | receiver_index = sorts.binary_search(users.data, ACCOUNT_NUMBER, receiver_account_number) 159 | 160 | receiver = users.data[receiver_index] 161 | receiver_balance = float(receiver[BALANCE]) 162 | receiver_name = receiver[LAST_NAME] + " " + receiver[MIDDLE_NAME] + " " + receiver[FIRST_NAME] 163 | print(f"Account number: {receiver_account_number}") 164 | print(f"Account holder: {receiver_name}") 165 | 166 | print("☞ Do you want to proceed? if YES press 1 or 2 if NO") 167 | user_choice = utils.get_yes_no_choice() 168 | if not user_choice: 169 | print("Finish action!!!") 170 | return users 171 | 172 | if user_choice == "2": 173 | print("Finish action!!!") 174 | return users 175 | 176 | user = users.data[user_index] 177 | user_balance = float(user[BALANCE]) 178 | transfer_money = _get_money(user_balance) 179 | if not transfer_money: 180 | print("Finish action!!!") 181 | return users 182 | 183 | user_name = user[LAST_NAME] + " " + user[MIDDLE_NAME] + " " + user[FIRST_NAME] 184 | 185 | print("Transaction information!!!") 186 | print(f"Sender: {user_name}") 187 | print(f"Received account number: {receiver_account_number}") 188 | print(f"Receiver: {receiver_name}") 189 | print(f"Amount transfer: {transfer_money}") 190 | 191 | print("Do you confirm?") 192 | user_choice = utils.get_yes_no_choice() 193 | if not user_choice: 194 | print("Finish action!!!") 195 | return users 196 | 197 | if user_choice == "2": 198 | print("Finish action!!!") 199 | return users 200 | 201 | receiver_balance += transfer_money 202 | user_balance -= transfer_money 203 | 204 | users.update_information(user_index, BALANCE, str(user_balance)) 205 | users.update_information(receiver_index, BALANCE, str(receiver_balance)) 206 | 207 | print("Successfully transfer the money!!!") 208 | print(f"Your balance now is: {user_balance}") 209 | 210 | return users 211 | 212 | def _get_money(balance: float) -> float: 213 | failed_attempt = FAILED_ATTEMPT 214 | money = 0 215 | while failed_attempt: 216 | money = input("Please choose the amount of money you want to transfer: ") 217 | if not utils.is_valid_balance(money): 218 | failed_attempt -= 1 219 | print("Invalid money, it must be a number!!!") 220 | print("You have %d try left!!!" % failed_attempt) 221 | elif float(money) > balance: 222 | failed_attempt -= 1 223 | print("Exceeds your current balance, try another one!!!") 224 | print("You have %d try left!!!" % failed_attempt) 225 | else: 226 | break 227 | 228 | if not failed_attempt: 229 | print("You failed to choose money amount many times, please wait few minutes to do it again") 230 | return 0 231 | 232 | return float(money) -------------------------------------------------------------------------------- /src/long_banking_system/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import string 4 | from os.path import exists 5 | 6 | import bcrypt 7 | from typing import List 8 | 9 | from consts import * 10 | 11 | 12 | def greeting() -> str: 13 | """ 14 | determine current time and choose appropriate greeting sentence 15 | """ 16 | 17 | current_time = datetime.datetime.now().time().strftime("%H:%M:%S") 18 | hour_min_sec = current_time.split(':') 19 | hours = int(hour_min_sec[0]) 20 | 21 | if 2 <= hours < 12: 22 | return "Good morning!!!" 23 | if 12 <= hours < 18: 24 | return "Good afternoon!!!" 25 | if 18 <= hours < 22: 26 | return "Good evening!!!" 27 | return "Hello!!!" 28 | 29 | def generate_hashed_password(plain_text_password: str) -> str: 30 | """ 31 | encrypted password with random salt using bcrypt algorithms 32 | """ 33 | 34 | return bcrypt.hashpw(str.encode(plain_text_password), bcrypt.gensalt(SALT_LEN)).decode() 35 | 36 | def check_password(plain_text_password: str, hashed_password: str) -> bool: 37 | """ 38 | check password with password saved in DB 39 | """ 40 | 41 | return bcrypt.checkpw(plain_text_password.encode(), hashed_password.encode()) 42 | 43 | def is_valid_name(name: str, max_len: int) -> bool: 44 | if len(name) > max_len or len(name) == 0: 45 | return False 46 | 47 | set_character = set(name) 48 | for character in set_character: 49 | if character in string.punctuation or character.isnumeric(): 50 | return False 51 | return True 52 | 53 | def get_temporal(temporal: str, max_time: int) -> str: 54 | failed_attempt = FAILED_ATTEMPT 55 | time = "" 56 | while failed_attempt: 57 | time = input("☞ Please enter the %s you was born: " % temporal) 58 | if not time.isnumeric(): 59 | failed_attempt -= 1 60 | print("Invalid type of year, you should enter a valid positive integer number") 61 | print("You have %d try left!!!" % failed_attempt) 62 | elif int(time) > max_time: 63 | failed_attempt -= 1 64 | print("Invalid year, you cannot born after %d" % max_time) 65 | print("You have %d try left!!!" % failed_attempt) 66 | else: 67 | break 68 | 69 | if not failed_attempt: 70 | print("You enter invalid choice many times, please wait a few minutes to try it again!!!") 71 | time = "" 72 | 73 | result = [""] 74 | if temporal == "year": 75 | result = ["0" for _ in range(4)] 76 | i = 3 77 | for digit in time[::-1]: 78 | result[i] = digit 79 | i -= 1 80 | else: 81 | result = ["0" for _ in range(2)] 82 | i = 1 83 | for digit in time[::-1]: 84 | result[i] = digit 85 | i -= 1 86 | 87 | return "".join(result) 88 | 89 | def is_valid_day(date: List[int], current_date: List[int]) -> bool: 90 | 91 | day, month, year = date 92 | if month < 1 or month > 12: 93 | print("You enter invalid month") 94 | return False 95 | 96 | max_day_in_month = 0 97 | if month in {1, 3, 5, 7, 8, 10, 12}: 98 | max_day_in_month = 31 99 | elif month in {4, 6, 9, 11}: 100 | max_day_in_month = 30 101 | elif year % 4 == 0 or year % 100 == 0 or year % 400 == 0: 102 | max_day_in_month = 29 103 | else: 104 | max_day_in_month = 28 105 | 106 | if day < 1 or day > max_day_in_month: 107 | print("You enter invalid day in that month") 108 | return False 109 | 110 | # need 18 or higher to create 111 | if year > current_date[2] - 18: 112 | print("You need to be 18 years old to create a new account") 113 | return False 114 | 115 | if year < current_date[2] - 18: 116 | return True 117 | 118 | if month > current_date[1]: 119 | print("You need to be 18 years old to create a new account") 120 | return False 121 | 122 | if month < current_date[1]: 123 | return True 124 | 125 | if day < current_date[0]: 126 | print("You need to be 18 years old to create a new account") 127 | return False 128 | 129 | return True 130 | 131 | def is_valid_phone_number(phone_number: str) -> bool: 132 | if len(phone_number) != PHONE_NUMBER_LEN: 133 | print("Phone number should have length %d\n" % 10) 134 | return False 135 | 136 | if phone_number[0] != "0": 137 | print("Phone number should start with 0\n") 138 | return False 139 | 140 | for digit in phone_number: 141 | if not digit.isnumeric(): 142 | print("%s is not a number\n" % digit) 143 | return False 144 | 145 | return True 146 | 147 | def is_valid_email(email: str) -> bool: 148 | 149 | return True 150 | 151 | def is_valid_password(password: str) -> bool: 152 | if len(password) < MIN_PASSWORD_LEN: 153 | print("Password's length should be at least 8 characters!!!") 154 | return False 155 | 156 | if len(password) > MAX_PASSWORD_LEN: 157 | print("Password's length should be smaller or equal than 100 characters!!!") 158 | return False 159 | 160 | have_number, have_lowercase, have_uppercase, have_specical_character = False, False, False, False 161 | for char in password: 162 | if char == " ": 163 | return False 164 | if char.isnumeric(): 165 | have_number = True 166 | elif char.islower(): 167 | have_lowercase = True 168 | elif char.isupper(): 169 | have_uppercase = True 170 | elif char in string.punctuation: 171 | have_specical_character = True 172 | 173 | return have_number and have_lowercase and have_uppercase and have_specical_character 174 | 175 | def is_valid_account_number(account_number: str, privilege: str) -> bool: 176 | if len(account_number) != ACCOUNT_CONFIGS[privilege][ACCOUNT_NUMBER_LEN]: 177 | return False 178 | 179 | for digit in account_number: 180 | if not digit.isnumeric(): 181 | return False 182 | 183 | return True 184 | 185 | def get_yes_no_choice() -> str: 186 | print(" ┌─────────────┐ ╭─────────────────╮ ") 187 | print(" │ L O N G │ │ ▶︎ 1 • YES │ ") 188 | print(" │ T U A N │ ├────────────────┬╯ ") 189 | print(" │ B A N K │ │ ▶︎ 2 • NO │ ") 190 | print(" └─────────────┘ ╰────────────────╯ ") 191 | 192 | failed_attempt = FAILED_ATTEMPT 193 | user_choice = "" 194 | while failed_attempt: 195 | user_choice = input("☞ Enter your choice: ") 196 | if user_choice not in YES_NO_CHOICES: 197 | failed_attempt -= 1 198 | print("Wrong choice!!! Please choose only 1 or 2") 199 | print("You have %d try left!!!" % failed_attempt) 200 | else: 201 | break 202 | 203 | if not failed_attempt: 204 | print("You enter wrong choice many times, please wait few minutes to do it again") 205 | return "" 206 | 207 | return user_choice 208 | 209 | def is_valid_balance(balance: str) -> bool: 210 | for digit in balance: 211 | if not digit.isnumeric() and digit != ".": 212 | return False 213 | 214 | return True 215 | 216 | def is_valid_message(message: str) -> bool: 217 | if len(message) > MESSAGE_MAX_CHARACTERS: 218 | print(f"Your message have more than {MESSAGE_MAX_CHARACTERS} characters") 219 | return False 220 | 221 | words = message.split(" ") 222 | if len(words) > MESSAGE_MAX_WORDS: 223 | print(f"Your message have more than {MESSAGE_MAX_WORDS} characters") 224 | return False 225 | 226 | return True 227 | 228 | def proceed_next() -> None: 229 | _ = input("Press anything if you want to proceed to the next") 230 | 231 | def get_data_from_json(filename: str): 232 | """ 233 | Reads information from the data file 234 | """ 235 | 236 | if not exists(filename): 237 | return {} 238 | 239 | file = open(filename, "r") 240 | data = file.read() 241 | 242 | # convert string json to python object 243 | return json.loads(data) 244 | 245 | def write_data_to_json(data, filename: str) -> None: 246 | """ 247 | Writes the data into the file with filename 248 | """ 249 | 250 | file = open(filename, "w") 251 | json_data = json.dumps(data) 252 | file.write(json_data) 253 | 254 | 255 | if __name__ == '__main__': 256 | print(greeting()) 257 | # print(generate_hashed_password("longdeptrai")) 258 | # print(check_password("longdeptrai", "$2b$12$nNeZrual6HCn2KSu8OroyenyizjXqckFn8UtOl5X.zkSAxFVO6/JS")) 259 | # # print(check_password("longdeptraii", "$2b$12$nNeZrual6HCn2KSu8OroyenyizjXqckFn8UtOl5X.zkSAxFVO6/JS")) 260 | # print(is_valid_name("""a"a""", 3)) 261 | # print(is_valid_name("!asds", 6)) 262 | # print(is_valid_name("long", 100)) 263 | # print(is_valid_name("asdkljafjlaskd", 100)) 264 | # print(is_valid_name("asdkljafjlaskd", 5)) 265 | # print(is_valid_name("1234asdvsxa", 1000)) 266 | # print(get_temporal("month", 12)) 267 | # print(is_valid_password("21101998Abc!")) 268 | # print(is_valid_phone_number("0123456789")) 269 | # print(is_valid_phone_number("012345678")) 270 | # print(is_valid_phone_number("1123456789")) 271 | # print(is_valid_phone_number("012345678a")) -------------------------------------------------------------------------------- /src/long_banking_system/account.py: -------------------------------------------------------------------------------- 1 | import time 2 | from collections import defaultdict 3 | from typing import List, Optional 4 | 5 | import maskpass 6 | 7 | import sorts 8 | from consts import * 9 | import utils 10 | import interface 11 | 12 | import models 13 | 14 | def authentication(privilege: str) -> (Optional[models.Users], int): 15 | """ 16 | login or create new banking account 17 | """ 18 | 19 | interface.clean_terminal_screen() 20 | 21 | print(utils.greeting()) 22 | print(" Welcome to Long's Bank\n") 23 | print("Please choose 1 if you already have an account or 2 if you want to create a new one") 24 | print(" ┌─────────────┐ ╭──────────────────╮ ") 25 | print(" │ │ │ ▶︎ 1 • Login │ ") 26 | print(" │ L O N G │ ├──────────────────┴────────────╮ ") 27 | print(" │ T U A N │ │ ▶︎ 2 • Create New Account │ ") 28 | print(" │ B A N K │ ├──────────────────┬────────────╯ ") 29 | print(" │ │ │ ▶︎ 3 • Exit │ ") 30 | print(" └─────────────┘ ╰──────────────────╯ ") 31 | 32 | 33 | failed_attempt = FAILED_ATTEMPT 34 | user_choice = "" 35 | while failed_attempt: 36 | user_choice = input("☞ Enter your choice: ") 37 | if user_choice not in AUTHENTICATION_CHOICES: 38 | failed_attempt -= 1 39 | print("Wrong choice!!! Please choose only 1 to 3") 40 | print("You have %d try left!!!" % failed_attempt) 41 | else: 42 | break 43 | 44 | if not failed_attempt: 45 | print("You enter wrong choice many times, please wait few minutes to do it again") 46 | return None, -1 47 | 48 | users = models.Users(privilege) 49 | user_index = -1 50 | 51 | if user_choice == "1": 52 | if not users.data: 53 | print("There are no account yet!!!") 54 | return None, -1 55 | 56 | print("You want to login your account") 57 | account_number = "" 58 | failed_attempt = FAILED_ATTEMPT 59 | while failed_attempt: 60 | account_number = input("☞ Please enter your bank account: ") 61 | if account_number not in users.users_set: 62 | failed_attempt -= 1 63 | print("Account does not exist!!! Please enter your own account") 64 | print("You have %d try left!!!" % failed_attempt) 65 | else: 66 | break 67 | 68 | if not failed_attempt: 69 | print("You enter wrong choice many times, please wait few minutes to login again") 70 | return None, -1 71 | 72 | user_index = sorts.binary_search(users.data, ACCOUNT_NUMBER, account_number) 73 | if user_index == -1: 74 | print("You enter wrong choice many times, please wait few minutes to login again") 75 | return None, -1 76 | 77 | user = users.data[user_index] 78 | print(utils.greeting()) 79 | print(" %s" % account_number) 80 | failed_attempt = FAILED_ATTEMPT 81 | hashed_password = user[PASSWORD] 82 | while failed_attempt: 83 | password = maskpass.askpass(prompt="☞ Please enter your password: ") 84 | if not utils.check_password(password, hashed_password): 85 | failed_attempt -= 1 86 | print("Wrong password!!! Please try another one") 87 | print("You have %d try left!!!" % failed_attempt) 88 | else: 89 | break 90 | 91 | if not failed_attempt: 92 | print("You enter wrong password many times, please wait few minutes to login again") 93 | return None, -1 94 | 95 | print("Successfully login!!!") 96 | print("Welcome back %s!!!" % user[FIRST_NAME]) 97 | 98 | if user_choice == "2": 99 | print("You want to create new bank account online") 100 | print("Please enter required information correctly, make sure all information you provide are true") 101 | 102 | first_name = get_name(FIRST_NAME_MAX_LEN, FAILED_ATTEMPT, "first") 103 | if not first_name: 104 | return None, -1 105 | 106 | # Some people may not have middle name 107 | middle_name = "" 108 | middle_name = get_name(MIDDLE_NAME_MAX_LEN, FAILED_ATTEMPT, "middle") 109 | 110 | last_name = get_name(LAST_NAME_MAX_LEN, FAILED_ATTEMPT, "last") 111 | if not last_name: 112 | return None, -1 113 | 114 | gender = get_gender(GENDER_SET_CHOICE) 115 | if not gender: 116 | return None, -1 117 | 118 | date_of_birth = get_date_of_birth() 119 | if not date_of_birth: 120 | return None, -1 121 | 122 | phone_number = get_phone_number() 123 | if not phone_number: 124 | return None, -1 125 | 126 | email = get_email() 127 | 128 | password = get_password(utils.generate_hashed_password("")) 129 | if not password: 130 | return None, -1 131 | 132 | user_information = defaultdict(str) 133 | user_information[FIRST_NAME] = first_name 134 | user_information[MIDDLE_NAME] = middle_name 135 | user_information[LAST_NAME] = last_name 136 | user_information[GENDER] = gender 137 | user_information[DATE_OF_BIRTH] = date_of_birth 138 | user_information[PHONE_NUMBER] = phone_number 139 | user_information[EMAIL] = email 140 | user_information[PASSWORD] = password 141 | user_information[BALANCE] = "0" 142 | user_index = users.create_new(user_information) 143 | 144 | print("Successfully create new account!!!") 145 | 146 | if user_choice == "3": 147 | return None, -1 148 | 149 | print("Move to the next step") 150 | 151 | return users, user_index 152 | 153 | def get_name(name_max_len: int, failed_attempt: int, kind: str) -> str: 154 | name = "" 155 | while failed_attempt: 156 | name = input("☞ Please enter your %s name: " % kind) 157 | if not utils.is_valid_name(name, name_max_len): 158 | failed_attempt -= 1 159 | print("Invalid %s name!!! Please try your real valid %s name" % (kind, kind)) 160 | print("You have %d try left!!!" % failed_attempt) 161 | else: 162 | break 163 | 164 | if not failed_attempt: 165 | print("You enter invalid %s name many times, please wait few minutes to create new account again") 166 | name = "" 167 | 168 | return name 169 | 170 | def get_gender(gender_list: List[str]) -> str: 171 | print("☞ Please choose among options below") 172 | print(" ┌─────────────┐ ╭──────────────────╮ ") 173 | print(" │ │ │ ▶︎ 1 • Male │ ") 174 | print(" │ L O N G │ ├──────────────────┴─╮ ") 175 | print(" │ T U A N │ │ ▶︎ 2 • Female │ ") 176 | print(" │ B A N K │ ├────────────────────┴╮ ") 177 | print(" │ │ │ ▶︎ 3 • Others │ ") 178 | print(" └─────────────┘ ╰─────────────────────╯ ") 179 | 180 | failed_attempt = FAILED_ATTEMPT 181 | gender = "" 182 | while failed_attempt: 183 | gender = input("☞ Please enter your choice about your gender: ") 184 | if gender not in gender_list: 185 | failed_attempt -= 1 186 | print("Invalid choice please read carefully, choose 1 for male, 2 for female and 3 for others") 187 | print("You have %d try left!!!" % failed_attempt) 188 | else: 189 | break 190 | if not failed_attempt: 191 | print("You enter invalid choice many times, please wait a few minutes to try it again!!!") 192 | return "" 193 | 194 | if gender == "1": 195 | return "male" 196 | 197 | if gender == "2": 198 | return "female" 199 | 200 | if gender == "3": 201 | return "others" 202 | 203 | return "" 204 | 205 | def get_date_of_birth() -> str: 206 | print("☞ Please enter the year, month, day when you was born respectively!!!") 207 | current_time = time.strftime("%d:%m:%Y") 208 | current_time_list = current_time.split(":") 209 | current_day, current_month, current_year = current_time_list 210 | 211 | year = utils.get_temporal("year", int(current_year)) 212 | if not year: 213 | return "" 214 | 215 | month = utils.get_temporal("month", 12) 216 | if not month: 217 | return "" 218 | 219 | day = utils.get_temporal("day", 31) 220 | if not day: 221 | return "" 222 | 223 | if not utils.is_valid_day([int(day), int(month), int(year)], 224 | [int(current_day), int(current_month), int(current_year)]): 225 | return "" 226 | 227 | return day + "/" + month + "/" + year 228 | 229 | def get_phone_number() -> str: 230 | failed_attempt = FAILED_ATTEMPT 231 | phone_number = "" 232 | while failed_attempt: 233 | phone_number = input("☞ Please enter your phone number: ") 234 | if not utils.is_valid_phone_number(phone_number): 235 | failed_attempt -= 1 236 | print("Invalid phone number, please, try it again!!!") 237 | print("You have %d try left!!!" % failed_attempt) 238 | else: 239 | break 240 | if not failed_attempt: 241 | print("You enter invalid choice many times, please wait a few minutes to try it again!!!") 242 | phone_number = "" 243 | 244 | return phone_number 245 | 246 | def get_email() -> str: 247 | print("☞ Do you want to add your email? if YES press 1 or 2 if NO") 248 | 249 | user_choice = utils.get_yes_no_choice() 250 | if not user_choice: 251 | return "" 252 | 253 | if user_choice == "2": 254 | return "" 255 | 256 | email = "" 257 | failed_attempt = FAILED_ATTEMPT 258 | while failed_attempt: 259 | email = input("☞ Please enter your email: ") 260 | if not utils.is_valid_email(email): 261 | failed_attempt -= 1 262 | print("Invalid email, please, try it again!!!") 263 | print("You have %d try left!!!" % failed_attempt) 264 | else: 265 | break 266 | 267 | if not failed_attempt: 268 | print("You enter invalid choice many times, please wait a few minutes to try it again!!!") 269 | email = "" 270 | 271 | return email 272 | 273 | def get_password(previous_password: str) -> str: 274 | password = "" 275 | failed_attempt = FAILED_ATTEMPT 276 | while failed_attempt: 277 | password = maskpass.askpass(prompt="☞ Please enter your password. It should be at least 8 characters, " 278 | "contain numbers, lowercase, uppercase letters, and special characters: ") 279 | if not utils.is_valid_password(password): 280 | failed_attempt -= 1 281 | print("Invalid password. Please, try it again!!!") 282 | print("You have %d try left!!!" % failed_attempt) 283 | elif utils.check_password(password, previous_password): 284 | failed_attempt -= 1 285 | print("You enter same password with your previous password") 286 | print("You have %d try left!!!" % failed_attempt) 287 | else: 288 | break 289 | 290 | if not failed_attempt: 291 | print("You enter invalid choice many times, please wait a few minutes to try it again!!!") 292 | password = "" 293 | 294 | failed_attempt = FAILED_ATTEMPT 295 | while failed_attempt: 296 | re_enter_password = maskpass.askpass(prompt="☞ Please re-enter your password: ") 297 | if password != re_enter_password: 298 | failed_attempt -= 1 299 | print("Different with your previous password. Please, try it again!!!") 300 | print("You have %d try left!!!" % failed_attempt) 301 | else: 302 | break 303 | 304 | if not failed_attempt: 305 | print("You enter invalid choice many times, please wait a few minutes to try it again!!!") 306 | password = "" 307 | 308 | return utils.generate_hashed_password(password) 309 | -------------------------------------------------------------------------------- /src/long_banking_system/services.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import account 4 | import sorts 5 | from consts import * 6 | import interface 7 | import messages 8 | import models 9 | import transactions 10 | import utils 11 | 12 | 13 | def users_services(users: models.Users, user_index: int, feedbacks_messages: messages.MessageQueue) -> \ 14 | (Optional[models.Users], Optional[messages.MessageQueue]): 15 | interface.clean_terminal_screen() 16 | interface.display_horizontal_line() 17 | 18 | print("What do you want to do next?") 19 | print("☞ Please choose among options below") 20 | print(" ┌─────────────┐ ╭───────────────────────────╮ ") 21 | print(" │ ╭┼┼╮ │ │ ▶︎ 1 • Get Information │ ") 22 | print(" │ ╰┼┼╮ │ ├───────────────────────────┴────╮ ") 23 | print(" │ ╰┼┼╯ │ │ ▶︎ 2 • Update Information │ ") 24 | print(" │ │ ├──────────────────────────────┬─╯ ") 25 | print(" │ L O N G │ │ ▶︎ 3 • Change Password │ ") 26 | print(" │ T U A N │ ├────────────────────────────┬─╯ ") 27 | print(" │ B A N K │ │ ▶︎ 4 • Delete Account │ ") 28 | print(" │ │ ├────────────────────────────┴────╮ ") 29 | print(" │ │ │ ▶︎ 5 • Perform Transaction │ ") 30 | print(" │ │ ├───────────────────────┬─────────╯ ") 31 | print(" │ ║│┃┃║║│┃║│║ │ │ ▶︎ 6 • Feedbacks │ ") 32 | print(" │ ║│┃┃║║│┃║│║ │ ├───────────────────────┴─╮ ") 33 | print(" │ │ │ ▶︎ 7 • Exit System │ ") 34 | print(" └─────────────┘ ╰─────────────────────────╯ ") 35 | 36 | failed_attempt = FAILED_ATTEMPT 37 | user_choice = "" 38 | while failed_attempt: 39 | user_choice = input("☞ Enter your choice: ") 40 | if user_choice not in USER_SERVICES_CHOICES: 41 | failed_attempt -= 1 42 | print("Wrong choice!!! Please choose only from 1 to 7") 43 | print("You have %d try left!!!" % failed_attempt) 44 | else: 45 | break 46 | 47 | if not failed_attempt: 48 | print("You enter wrong choice many times, please wait few minutes to do it again") 49 | return None, feedbacks_messages 50 | 51 | if user_choice == "1": 52 | _display_user_information(users.data[user_index]) 53 | 54 | if user_choice == "2": 55 | users = _update_information(users, user_index) 56 | 57 | if user_choice == "3": 58 | users = _update_password(users, user_index) 59 | 60 | if user_choice == "4": 61 | users.delete_user(user_index) 62 | return None, feedbacks_messages 63 | 64 | if user_choice == "5": 65 | users = transactions.transaction_services(users, user_index) 66 | 67 | if user_choice == "6": 68 | feedbacks_messages = messages.add_message(feedbacks_messages, users.data[user_index][ACCOUNT_NUMBER]) 69 | 70 | if user_choice == "7": 71 | return None, feedbacks_messages 72 | 73 | return users, feedbacks_messages 74 | 75 | def admins_services(admins: models.Users, users: models.Users, user_index: int, 76 | feedbacks_messages: messages.MessageQueue) -> \ 77 | (Optional[models.Users], Optional[messages.MessageQueue]): 78 | interface.clean_terminal_screen() 79 | interface.display_horizontal_line() 80 | 81 | print("What operation do you want to do?") 82 | print("☞ Please choose among admins' options below") 83 | print(" ┌─────────────┐ ╭───────────────────────────╮ ") 84 | print(" │ ╭┼┼╮ │ │ ▶︎ 1 • Get Information │ ") 85 | print(" │ ╰┼┼╮ │ ├───────────────────────────┴────╮ ") 86 | print(" │ ╰┼┼╯ │ │ ▶︎ 2 • Update Information │ ") 87 | print(" │ │ ├──────────────────────────────┬─╯ ") 88 | print(" │ L O N G │ │ ▶︎ 3 • Change Password │ ") 89 | print(" │ T U A N │ ├────────────────────────────┬─╯ ") 90 | print(" │ B A N K │ │ ▶︎ 4 • Delete Account │ ") 91 | print(" │ │ ├────────────────────────────┴───────╮ ") 92 | print(" │ │ │ ▶︎ 5 • Get User's Information │ ") 93 | print(" │ │ ├────────────────────────────────────┴─╮ ") 94 | print(" │ │ │ ▶︎ 6 • Get Information by Field │ ") 95 | print(" │ │ ├────────────────────────────┬─────────╯ ") 96 | print(" │ ║│┃┃║║│┃║│║ │ │ ▶︎ 7 • Read Feedbacks │ ") 97 | print(" │ ║│┃┃║║│┃║│║ │ ├─────────────────────────┬──╯ ") 98 | print(" │ │ │ ▶︎ 8 • Exit System │ ") 99 | print(" └─────────────┘ ╰─────────────────────────╯ ") 100 | 101 | failed_attempt = FAILED_ATTEMPT 102 | admin_choice = "" 103 | while failed_attempt: 104 | admin_choice = input("☞ Enter your choice: ") 105 | if admin_choice not in ADMIN_SERVICES_CHOICES: 106 | failed_attempt -= 1 107 | print("Wrong choice!!! Please choose only from 1 to 8") 108 | print("You have %d try left!!!" % failed_attempt) 109 | else: 110 | break 111 | 112 | if not failed_attempt: 113 | print("You enter wrong choice many times, please wait few minutes to do it again") 114 | return None, feedbacks_messages 115 | 116 | if admin_choice == "1": 117 | _display_user_information(admins.data[user_index]) 118 | 119 | if admin_choice == "2": 120 | admins = _update_information(admins, user_index) 121 | 122 | if admin_choice == "3": 123 | users = _update_password(admins, user_index) 124 | 125 | if admin_choice == "4": 126 | users.delete_user(user_index) 127 | return None, feedbacks_messages 128 | 129 | if admin_choice == "5": 130 | account_number = _get_account_number(users) 131 | if not account_number: 132 | return users, feedbacks_messages 133 | 134 | user_index = sorts.binary_search(users.data, ACCOUNT_NUMBER, account_number) 135 | if user_index == -1: 136 | print("Users not exists, please try again!!!") 137 | return users, feedbacks_messages 138 | 139 | _display_user_information(users.data[user_index]) 140 | 141 | if admin_choice == "6": 142 | _get_user_information_by_field(users) 143 | 144 | if admin_choice == "7": 145 | feedbacks_messages = messages.read_message(feedbacks_messages) 146 | 147 | if admin_choice == "8": 148 | return None, feedbacks_messages 149 | 150 | return admins, feedbacks_messages 151 | 152 | def _display_user_information(user: dict) -> None: 153 | """ 154 | Display user's information 155 | """ 156 | 157 | interface.display_horizontal_line() 158 | 159 | print("Here is your information. You are welcome!!!") 160 | print("Full name: %s %s %s" % (user[LAST_NAME], user[MIDDLE_NAME], user[FIRST_NAME])) 161 | print("Gender: %s" % user[GENDER]) 162 | print("Date of birth: %s" % user[DATE_OF_BIRTH]) 163 | print("Phone number: %s" % user[PHONE_NUMBER]) 164 | print("Email: %s" % user[EMAIL]) 165 | print("Account number: %s" % user[ACCOUNT_NUMBER]) 166 | print("Issued date: %s" % user[ISSUED_DATE]) 167 | 168 | interface.display_horizontal_line() 169 | utils.proceed_next() 170 | 171 | 172 | def _update_information(users: models.Users, user_index: int) -> models.Users: 173 | print("What information you want to edit?") 174 | print("☞ Please choose among information listed below") 175 | print(" ┌─────────────┐ ╭────────────────────────╮ ") 176 | print(" │ ╭┼┼╮ │ │ ▶︎ 1 • First Name │ ") 177 | print(" │ ╰┼┼╮ │ ├────────────────────────┴╮ ") 178 | print(" │ ╰┼┼╯ │ │ ▶︎ 2 • Middle Name │ ") 179 | print(" │ │ ├───────────────────────┬─╯ ") 180 | print(" │ L O N G │ │ ▶︎ 3 • Last Name │ ") 181 | print(" │ T U A N │ ├────────────────────┬──╯ ") 182 | print(" │ B A N K │ │ ▶︎ 4 • Gender │ ") 183 | print(" │ │ ├────────────────────┴──────╮ ") 184 | print(" │ │ │ ▶︎ 5 • Date of Birth │ ") 185 | print(" │ │ ├──────────────────────────┬╯ ") 186 | print(" │ ║│┃┃║║│┃║│║ │ │ ▶︎ 6 • Phone Number │ ") 187 | print(" │ ║│┃┃║║│┃║│║ │ ├───────────────────┬──────╯ ") 188 | print(" │ │ │ ▶︎ 7 • Email │ ") 189 | print(" │ │ ├──────────────────┬╯ ") 190 | print(" │ │ │ ▶︎ 8 • Exit │ ") 191 | print(" └─────────────┘ ╰──────────────────╯ ") 192 | 193 | failed_attempt = FAILED_ATTEMPT 194 | user_choice = "" 195 | while failed_attempt: 196 | user_choice = input("☞ Enter your choice: ") 197 | if user_choice not in USER_UPDATE_INFORMATION_CHOICES: 198 | failed_attempt -= 1 199 | print("Wrong choice!!! Please choose only from 1 to 8") 200 | print("You have %d try left!!!" % failed_attempt) 201 | else: 202 | break 203 | 204 | if not failed_attempt: 205 | print("You enter wrong choice many times, please wait few minutes to do it again") 206 | return users 207 | 208 | if user_choice == "1": 209 | first_name = account.get_name(FIRST_NAME_MAX_LEN, FAILED_ATTEMPT, "first") 210 | if not first_name: 211 | return users 212 | 213 | users.update_information(user_index, FIRST_NAME, first_name) 214 | print("Successfully update your first name!!!") 215 | 216 | if user_choice == "2": 217 | middle_name = account.get_name(FIRST_NAME_MAX_LEN, FAILED_ATTEMPT, "middle") 218 | if not middle_name: 219 | return users 220 | 221 | users.update_information(user_index, MIDDLE_NAME, middle_name) 222 | print("Successfully update your middle name!!!") 223 | 224 | if user_choice == "3": 225 | last_name = account.get_name(FIRST_NAME_MAX_LEN, FAILED_ATTEMPT, "last") 226 | if not last_name: 227 | return users 228 | 229 | users.update_information(user_index, LAST_NAME, last_name) 230 | print("Successfully update your last name!!!") 231 | 232 | if user_choice == "4": 233 | gender = account.get_gender(GENDER_SET_CHOICE) 234 | if not gender: 235 | return users 236 | 237 | users.update_information(user_index, GENDER, gender) 238 | print("Successfully update your gender!!!") 239 | 240 | if user_choice == "5": 241 | date_of_birth = account.get_date_of_birth() 242 | if not date_of_birth: 243 | return users 244 | 245 | users.update_information(user_index, DATE_OF_BIRTH, date_of_birth) 246 | print("Successfully update your date of birth!!!") 247 | 248 | if user_choice == "6": 249 | phone_number = account.get_phone_number() 250 | if not phone_number: 251 | return users 252 | 253 | users.update_information(user_index, PHONE_NUMBER, phone_number) 254 | print("Successfully update your phone number!!!") 255 | 256 | if user_choice == "7": 257 | email = account.get_email() 258 | if not email: 259 | return users 260 | 261 | users.update_information(user_index, EMAIL, email) 262 | print("Successfully update your email!!!") 263 | 264 | if user_choice == "8": 265 | return users 266 | 267 | print("Please check your new information") 268 | _display_user_information(users.data[user_index]) 269 | 270 | return users 271 | 272 | def _update_password(users: models.Users, user_index: int) -> models.Users: 273 | new_password = account.get_password(users.data[user_index][PASSWORD]) 274 | if not new_password: 275 | return users 276 | 277 | users.update_information(user_index, PASSWORD, new_password) 278 | print("Successfully update new password") 279 | 280 | return users 281 | 282 | def _get_account_number(users: models.Users) -> str: 283 | failed_attempt = FAILED_ATTEMPT 284 | account_number = "" 285 | while failed_attempt: 286 | account_number = input("☞ Please enter the user's account number: ") 287 | if not utils.is_valid_account_number(account_number, "users"): 288 | failed_attempt -= 1 289 | print("Invalid account number, please, try it again!!!") 290 | print("You have %d try left!!!" % failed_attempt) 291 | elif account_number not in users.users_set: 292 | failed_attempt -= 1 293 | print("account number does not exist, please, try it again!!!") 294 | print("You have %d try left!!!" % failed_attempt) 295 | else: 296 | break 297 | 298 | if not failed_attempt: 299 | print("You enter invalid choice many times, please wait a few minutes to try it again!!!") 300 | phone_number = "" 301 | 302 | return account_number 303 | 304 | def _get_user_information_by_field(users: models.Users) -> None: 305 | print("☞ Please choose among options below") 306 | print(" ┌─────────────┐ ╭────────────────────────────╮ ") 307 | print(" │ │ │ ▶︎ 1 • Account Number │ ") 308 | print(" │ L O N G │ ├─────────────────────┬──────╯ ") 309 | print(" │ T U A N │ │ ▶︎ 2 • Balance │ ") 310 | print(" │ B A N K │ ├─────────────────────┴──╮ ") 311 | print(" │ │ │ ▶︎ 3 • First Name │ ") 312 | print(" └─────────────┘ ╰────────────────────────╯ ") 313 | 314 | failed_attempt = FAILED_ATTEMPT 315 | user_choice = "" 316 | while failed_attempt: 317 | user_choice = input("☞ Please enter the user's account number: ") 318 | if user_choice not in FIELDS_SEARCH: 319 | failed_attempt -= 1 320 | print(f"You cannot search by field: {field}, please, try it again!!!") 321 | print("You have %d try left!!!" % failed_attempt) 322 | else: 323 | break 324 | 325 | if not failed_attempt or not user_choice: 326 | print("You enter invalid choice many times, please wait a few minutes to try it again!!!") 327 | phone_number = "" 328 | 329 | field = FIELDS_SEARCH[user_choice] 330 | sort_data = sorts.Sorting(users.data, field, "").arr 331 | if not sort_data: 332 | print("There are no users at that time in database") 333 | return 334 | 335 | for user in sort_data: 336 | _display_user_information(user) --------------------------------------------------------------------------------