From ad74e05c8a888fbd265e93609e427a34b25648c8 Mon Sep 17 00:00:00 2001 From: Akshar Goyal Date: Thu, 23 Oct 2025 15:27:57 +0530 Subject: [PATCH 1/6] feat(cli): add banking system cli app and fix #166 --- .../MiniProjects/banking-system/.gitignore | 2 + .../MiniProjects/banking-system/Cargo.lock | 440 ++++++++++++++++++ .../MiniProjects/banking-system/Cargo.toml | 9 + .../cli/MiniProjects/banking-system/README.md | 56 +++ .../MiniProjects/banking-system/src/cli.rs | 35 ++ .../banking-system/src/database.rs | 290 ++++++++++++ .../MiniProjects/banking-system/src/lib.rs | 6 + .../MiniProjects/banking-system/src/luhn.rs | 196 ++++++++ .../MiniProjects/banking-system/src/main.rs | 70 +++ .../MiniProjects/banking-system/src/menu.rs | 96 ++++ 10 files changed, 1200 insertions(+) create mode 100644 Domains/cli/MiniProjects/banking-system/.gitignore create mode 100644 Domains/cli/MiniProjects/banking-system/Cargo.lock create mode 100644 Domains/cli/MiniProjects/banking-system/Cargo.toml create mode 100644 Domains/cli/MiniProjects/banking-system/README.md create mode 100644 Domains/cli/MiniProjects/banking-system/src/cli.rs create mode 100644 Domains/cli/MiniProjects/banking-system/src/database.rs create mode 100644 Domains/cli/MiniProjects/banking-system/src/lib.rs create mode 100644 Domains/cli/MiniProjects/banking-system/src/luhn.rs create mode 100644 Domains/cli/MiniProjects/banking-system/src/main.rs create mode 100644 Domains/cli/MiniProjects/banking-system/src/menu.rs diff --git a/Domains/cli/MiniProjects/banking-system/.gitignore b/Domains/cli/MiniProjects/banking-system/.gitignore new file mode 100644 index 00000000..528af2f0 --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/.gitignore @@ -0,0 +1,2 @@ +target/ +*.s3db diff --git a/Domains/cli/MiniProjects/banking-system/Cargo.lock b/Domains/cli/MiniProjects/banking-system/Cargo.lock new file mode 100644 index 00000000..5a877f7e --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/Cargo.lock @@ -0,0 +1,440 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "banking-system" +version = "0.1.0" +dependencies = [ + "clap", + "rand", + "rusqlite", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Domains/cli/MiniProjects/banking-system/Cargo.toml b/Domains/cli/MiniProjects/banking-system/Cargo.toml new file mode 100644 index 00000000..301de984 --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "banking-system" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.5.50", features = ["derive"] } +rand = "0.9.2" +rusqlite = { version = "0.37.0", features = ["backup"] } diff --git a/Domains/cli/MiniProjects/banking-system/README.md b/Domains/cli/MiniProjects/banking-system/README.md new file mode 100644 index 00000000..98c9de7d --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/README.md @@ -0,0 +1,56 @@ +# Simple Banking App + +**Contributor:** AksharGoyal + +## Overview +A banking system application built in Rust. + +## Tech stack +- Rust + +## Prerequisites +- Rust + +## Quickstart +1. Make sure you are in banking-system directory. Then run +```sh +cargo install --path . +``` + +That's it! You can now use the command `banking-system` command in your cli to do simple bank-related operations. + +2. You can also add your own tests and check them: +```sh +cargo test -- --test-threads=1 +``` + +## banking-system subcommands + +You can run the following subcommands (ex: create) in 2 ways. +```sh +banking-system create # or +cargo run -- create +``` + +- create — Creates a new account with randomly generated account number and PIN +- login — Logins to an existing account to allow user to do banking operations +- delete - Deletes the existing account + +## Examples + +Create an account: +```sh +banking-system create +>>> YOUR NEW ACCOUNT: `6046334535` +>>> YOUR PIN: `688154` +``` + +Login into an existing account: +```sh +banking-system login 6046334535 688154 +``` + +Delete an existing account +```sh +banking-system delete 6046334535 688154 +``` diff --git a/Domains/cli/MiniProjects/banking-system/src/cli.rs b/Domains/cli/MiniProjects/banking-system/src/cli.rs new file mode 100644 index 00000000..bf9dd512 --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/src/cli.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Unlicense + +use clap::{Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[command(name = "bank", version)] +pub struct Opts { + /// Account subcommand + #[command(subcommand)] + pub account: AccountOpts, +} + +#[derive(Subcommand, Debug)] +#[command(name = "account", about = "A simple bank system in Rust")] +pub enum AccountOpts { + /// Login into account with account number. This will get you to the Main Menu. + #[command(name = "login")] + Login { + #[arg(help = "Account number of user")] + account: String, + #[arg(help = "PIN of the account")] + pin: String, + }, + /// Delete into account with account number. Your account will be gone forever. + #[command(name = "delete")] + Delete { + #[arg(help = "Account number of user")] + account: String, + #[arg(help = "PIN of the account.")] + pin: String, + }, + /// Create new account. This will have a randomly generated PIN. + #[command(name = "create")] + Create, +} \ No newline at end of file diff --git a/Domains/cli/MiniProjects/banking-system/src/database.rs b/Domains/cli/MiniProjects/banking-system/src/database.rs new file mode 100644 index 00000000..54591356 --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/src/database.rs @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: Unlicense + +use std::path::PathBuf; + +use crate::luhn::AccountNumber; + +use rand::rng; +use rand::prelude::*; +use rusqlite::{Connection, Result}; + +#[derive(Debug)] +pub struct Account { + pub id: u64, + pub account_number: String, + pub balance: usize, + pub pin: String, +} + +fn database_path() -> PathBuf { + PathBuf::from("bank.s3db") +} + +pub fn initialise_bankdb() -> Result { + let db = Connection::open(database_path())?; + + let command = "CREATE TABLE IF NOT EXISTS account( +id INTEGER PRIMARY KEY, +account_number TEXT, +pin TEXT DEFAULT '000000', +balance INTEGER DEFAULT 0 +) +"; + + db.execute(command, ())?; + Ok(db) +} + +pub fn create_account(data: &AccountNumber, balance: u64) -> Result<()> { + let db = initialise_bankdb()?; + let account_number = data.to_string(); + let mut stmt = db.prepare("SELECT id, account_number, balance, pin FROM account")?; + let accounts = stmt.query_map([], |row| { + Ok(Account { + id: row.get(0)?, + account_number: row.get(1)?, + balance: row.get(2)?, + pin: row.get(3)?, + }) + })?; + + let get_latest_max_id = { + let mut x = 0; + for account in accounts.flatten() { + if account.id > x { + x = account.id + } + } + x + }; + + let newest_max_id = get_latest_max_id + 1; + let mut rng = rng(); + let mut pin: Vec = Vec::new(); + + // Six digit pin + for _ in 1..=6 { + let y = rng.random_range(0..=9).to_string(); + pin.push(y); + } + + let pin: String = String::from_iter(pin); + + let new_account = Account { + id: newest_max_id, + account_number, + balance: balance.try_into().unwrap(), + pin, + }; + + db.execute( + "INSERT INTO account (id, account_number, pin, balance) VALUES (?1, ?2, ?3, ?4)", + ( + &new_account.id, + &new_account.account_number, + &new_account.pin, + &new_account.balance, + ), + )?; + Ok(()) +} + +pub fn deposit(amount: &str, pin: &str, account_number: &str) -> Result<()> { + let db = initialise_bankdb()?; + let query_string = format!( + "SELECT pin FROM account where account_number='{}';", + account_number + ); + + let pin_from_db: String = db.query_row(&query_string, [], |row| row.get(0))?; + + let correct_pin = { pin_from_db == pin }; + + if correct_pin { + db.execute( + "UPDATE account SET balance = balance + ?1 WHERE account_number=?2", + (amount, account_number), + )?; + + let query_string = format!( + "SELECT balance FROM account where account_number='{}';", + account_number + ); + + let amount_from_db: usize = db.query_row(&query_string, [], |row| row.get(0))?; + + println!( + "The account number `{}` now has a balance of `{}`.\n", + &account_number, &amount_from_db + ); + } else { + eprintln!("Wrong pin. Try again..."); + } + Ok(()) +} + +pub fn transfer( + amount: &str, + pin: &str, + account_number1: &str, + account_number2: &str, +) -> Result<()> { + if *account_number1 == *account_number2 { + eprintln!("Cannot perform a transfer to the same account!"); + return Ok(()); + }; + + let db = initialise_bankdb()?; + let query_string = format!( + "SELECT pin FROM account where account_number='{}';", + account_number1 + ); + + let pin_from_db: String = db.query_row(&query_string, [], |row| row.get(0))?; + + let correct_pin = { pin_from_db == pin }; + if correct_pin { + let query_string = format!( + "SELECT balance FROM account where account_number='{}';", + account_number1 + ); + + let amount_from_db: usize = db.query_row(&query_string, [], |row| row.get(0))?; + + println!( + "The account number `{}` has a balance of `{}`.\n", + &account_number1, &amount_from_db + ); + + let amount = amount + .parse::() + .expect("Not able to parse string to usize"); + + if amount > amount_from_db { + eprintln!( + "You are trying to transfer that exceeds your current balance... aborting...\n" + ); + } else { + // Add money to account 2 + db.execute( + "UPDATE account SET balance = balance + ?1 WHERE account_number=?2", + (amount, account_number2), + )?; + + // Subtract money from account 1 + db.execute( + "UPDATE account SET balance = balance - ?1 WHERE account_number=?2", + (amount, account_number1), + )?; + + let query_string = format!( + "SELECT balance FROM account where account_number='{}';", + account_number1 + ); + + let amount_from_db: usize = db.query_row(&query_string, [], |row| row.get(0))?; + + println!( + "The account number `{}` now has a balance of `{}`.\n", + &account_number1, &amount_from_db + ); + }; + } else { + eprintln!("Wrong pin. Try again..."); + } + Ok(()) +} + +pub fn withdraw(amount: &str, pin: &str, account_number: &str) -> Result<()> { + let db = initialise_bankdb()?; + let query_string = format!( + "SELECT pin FROM account where account_number='{}';", + account_number + ); + + let pin_from_db: String = db.query_row(&query_string, [], |row| row.get(0))?; + + let correct_pin = { pin_from_db == pin }; + + if correct_pin { + let query_string = format!( + "SELECT balance FROM account where account_number='{}';", + account_number + ); + + let amount_from_db: usize = db.query_row(&query_string, [], |row| row.get(0))?; + + println!( + "The account number `{}` has a balance of `{}`.\n", + &account_number, &amount_from_db + ); + + let amount = amount + .parse::() + .expect("Not able to parse string to usize"); + + if amount > amount_from_db { + eprintln!( + "You are trying to withdraw that exceeds your current deposit... aborting...\n" + ); + } else { + db.execute( + "UPDATE account SET balance = balance - ?1 WHERE account_number=?2", + (amount, account_number), + )?; + + let query_string = format!( + "SELECT balance FROM account where account_number='{}';", + account_number + ); + + let amount_from_db: usize = db.query_row(&query_string, [], |row| row.get(0))?; + + println!( + "The account number `{}` now has a balance of `{}`.\n", + &account_number, &amount_from_db + ); + }; + } else { + eprintln!("Wrong pin. Try again..."); + } + Ok(()) +} + +pub fn delete_account(account_number: &str, pin: &str) -> Result<()> { + let db = initialise_bankdb()?; + let query_string = format!( + "SELECT pin FROM account where account_number='{}';", + &account_number + ); + + let pin_from_db: String = db.query_row(&query_string, [], |row| row.get(0))?; + let correct_pin = { pin_from_db == pin }; + + if correct_pin { + db.execute( + "DELETE FROM account WHERE account_number=?1", + (account_number,), + )?; + println!("DELETED ACCOUNT: {}", &account_number); + } else { + eprintln!("Wrong pin. Try again..."); + } + Ok(()) +} + +pub fn show_balance(account_number: &str) -> Result<()> { + let db = initialise_bankdb()?; + let query_string = format!( + "SELECT balance FROM account where account_number='{}';", + account_number + ); + + let amount_from_db: usize = db.query_row(&query_string, [], |row| row.get(0))?; + + println!( + "The account number `{}` now has a balance of `{}`.\n", + &account_number, &amount_from_db + ); + Ok(()) +} \ No newline at end of file diff --git a/Domains/cli/MiniProjects/banking-system/src/lib.rs b/Domains/cli/MiniProjects/banking-system/src/lib.rs new file mode 100644 index 00000000..b4262942 --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/src/lib.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Unlicense + +pub mod cli; +pub mod database; +pub mod luhn; +pub mod menu; \ No newline at end of file diff --git a/Domains/cli/MiniProjects/banking-system/src/luhn.rs b/Domains/cli/MiniProjects/banking-system/src/luhn.rs new file mode 100644 index 00000000..ec39943c --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/src/luhn.rs @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: Unlicense + +use rand::rng; +use rand::prelude::*; +use std::fmt::Display; +use std::str::FromStr; + +/// The account number uses random number generation +/// for the payload. This payload is then calculated +/// to produce the check digit. +#[derive(Debug)] +pub struct AccountNumber { + /// Randomly generated number. Each digit is within 0..9 + pub(crate) payload: Vec, + /// Check digit using the Luhn formula + pub(crate) check_digit: u8, +} + +impl Default for AccountNumber { + fn default() -> Self { + Self::new(10) + } +} + +impl FromStr for AccountNumber { + type Err = std::io::ErrorKind; + + fn from_str(s: &str) -> Result { + if verify(s) { + let mut payload: Vec = s + .chars() + .map(|d| { + d.to_digit(10) + .expect("Not able to create u32 from character") as u8 + }) + .collect(); + let check_digit = payload.pop().expect("Cannot pop. Got an empty vector"); + Ok(Self { + payload, + check_digit, + }) + } else { + eprintln!("{s} is not a valid account number"); + Err(std::io::ErrorKind::InvalidData) + } + } +} + +impl Display for AccountNumber { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut account_number: String = self.payload.iter().map(|d| d.to_string()).collect(); + account_number.push_str(&self.check_digit.to_string()); + write!(f, "{}", account_number) + } +} + +impl AccountNumber { + pub fn new(length: usize) -> Self { + let mut payload: Vec = Vec::new(); + for _ in 1..=length - 1 { + let mut rng = rng(); + let zero_to_nine: u8 = rng.random_range(0..=9); + payload.push(zero_to_nine); + } + + let check_digit = get_check_digit(&payload); + + Self { + payload, + check_digit, + } + } + + pub fn check_digit(&self) -> u8 { + get_check_digit(&self.payload) + } + + pub fn human_readable(&self) -> String { + let mut payload: String = self.payload.iter().map(|d| d.to_string()).collect(); + payload.push_str(&self.check_digit.to_string()); + payload + } +} + +pub fn verify(account_number: &str) -> bool { + let account_number = account_number.to_string(); + let digits: Vec = account_number.trim().chars().collect(); + + let mut payload: Vec = digits + .iter() + .map(|digit| digit.to_digit(10).expect("Not a number") as u8) + .collect(); + + let check_digit = payload.pop().expect("This shouldn't be an empty iterator"); + + get_check_digit(&payload) == check_digit +} + +fn get_check_digit(payload: &[u8]) -> u8 { + let mut new_payload = payload.iter().copied(); + let mut luhn_sum = 0; + let mut index = 0; + + while let Some(item) = new_payload.next_back() { + let divisible_by_2 = { (index as f32 % 2_f32) == 0_f32 }; + if divisible_by_2 { + let mul = item * 2; + + let digits: Vec = mul + .to_string() + .chars() + .map(|d| d.to_digit(10).expect("Not a number character") as u8) + .collect(); + + let sum = { + let mut x = 0; + for digit in digits { + let y: u8 = digit; + x += y; + } + x + }; + + luhn_sum += sum; + } else { + luhn_sum += item; + }; + index += 1; + } + + 10 - (luhn_sum % 10) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + /// Valid account numbers in different lengths + fn valid_account_numbers_in_different_lengths() { + let accounts = [ + AccountNumber::from_str("35001576202"), + AccountNumber::from_str("8536276945"), + AccountNumber::from_str("20024"), + AccountNumber::from_str("26"), + ]; + + for account in accounts { + assert!(account.is_ok()); + } + } + + #[test] + fn valid_account_numbers_fixed_length() { + let valid_account_numbers = [ + "2334841596", + "5072686164", + "8330789085", + "2303133926", + "7730632457", + "1310866767", + "9083062142", + "8936042657", + "3188178648", + "1513312791", + "0204434294", + ]; + + for v in valid_account_numbers { + let account = AccountNumber::from_str(v); + assert!(account.is_ok()); + } + } + + #[test] + fn invalid_account_numbers_fixed_length() { + let invalid_account_numbers = [ + "2334841592", + "5072686163", + "8330789084", + "2303133925", + "7730632456", + "1310866766", + "9083062141", + "8936042656", + "3188178647", + "1513312790", + "0204434293", + ]; + + for v in invalid_account_numbers { + let account = AccountNumber::from_str(v); + assert!(account.is_err()); + } + } +} \ No newline at end of file diff --git a/Domains/cli/MiniProjects/banking-system/src/main.rs b/Domains/cli/MiniProjects/banking-system/src/main.rs new file mode 100644 index 00000000..68b8613a --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/src/main.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Unlicense + +use banking_system::cli; +use banking_system::database; +use banking_system::luhn::AccountNumber; +use banking_system::menu; +use clap::Parser; +use rusqlite::Result; + +fn main() -> Result<()> { + let cli = cli::Opts::parse(); + + match cli.account { + cli::AccountOpts::Login { account, pin } => { + let db = database::initialise_bankdb()?; + let query_string = format!( + "SELECT pin FROM account where account_number='{}';", + account + ); + + let pin_from_db: Result = db.query_row(&query_string, [], |row| row.get(0)); + match pin_from_db { + Ok(p) => { + if p == pin { + menu::prompt(&account).expect("Something went wrong"); + } else { + eprintln!("Wrong pin... try again"); + }; + } + Err(_) => { + eprintln!("No such account"); + } + }; + } + cli::AccountOpts::Delete { account, pin } => { + database::delete_account(&account, &pin)?; + } + cli::AccountOpts::Create => { + let mut new_account = AccountNumber::default(); + + let db = database::initialise_bankdb()?; + loop { + let query_string = format!( + "SELECT 1 FROM account where account_number='{}';", + new_account + ); + + match db.query_row(&query_string, [], |row| row.get::(0)) { + Ok(_) => { + new_account = AccountNumber::default(); + } + Err(_) => break, + } + } + + let _ = database::create_account(&new_account, 0); + + let query_string = format!( + "SELECT pin FROM account where account_number='{}';", + &new_account + ); + let pin_from_db: String = db.query_row(&query_string, [], |row| row.get(0))?; + println!( + "YOUR NEW ACCOUNT: `{}`\nYOUR PIN: `{}`\n", + &new_account, &pin_from_db + ); + } + }; + Ok(()) +} \ No newline at end of file diff --git a/Domains/cli/MiniProjects/banking-system/src/menu.rs b/Domains/cli/MiniProjects/banking-system/src/menu.rs new file mode 100644 index 00000000..f6306f61 --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/src/menu.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Unlicense + +use crate::database; +use std::io::BufRead; + +pub fn prompt(account_number: &str) -> std::io::Result<()> { + let prompt_text = "0) Show Current Balance +1) Deposit Money +2) Transfer Money +3) Withdraw Money +4) Delete Account +5) Exit"; + + loop { + println!("{}", &prompt_text); + let stdin = std::io::stdin(); + let mut query = String::new(); + let mut handle = stdin.lock(); + + handle.read_line(&mut query)?; + + let query = query.trim(); + + if query == "5" { + eprintln!("Exiting bank machine..."); + break; + } else if query == "4" { + println!("You are going to delete your account..."); + println!("Please input the pin:"); + let mut pin = String::new(); + handle.read_line(&mut pin)?; + let pin = pin.trim(); + + database::delete_account(account_number, pin) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + eprintln!("Account is not accessible. Exiting..."); + break; + } else if query == "3" { + println!("Please input the amount:"); + let mut amount = String::new(); + handle.read_line(&mut amount)?; + let amount = amount.trim(); + println!("The amount you wanted to withdraw: {}\n", &amount); + + println!("Please input the pin:"); + let mut pin = String::new(); + handle.read_line(&mut pin)?; + let pin = pin.trim(); + + database::withdraw(amount, pin, account_number) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + } else if query == "2" { + println!("Please input the amount:"); + let mut amount = String::new(); + handle.read_line(&mut amount)?; + let amount = amount.trim(); + println!("The amount you wanted to transfer: {}\n", &amount); + + println!("Please input the account number of the recipient:"); + let mut account_number2 = String::new(); + handle.read_line(&mut account_number2)?; + let account_number2 = account_number2.trim(); + println!( + "The account number you want to send is {} and the amount to transfer is {}\n", + &account_number2, &amount + ); + + println!("Please input your pin:"); + let mut pin = String::new(); + handle.read_line(&mut pin)?; + let pin = pin.trim(); + database::transfer(amount, pin, account_number, account_number2) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + } else if query == "1" { + println!("Please input the amount:"); + let mut amount = String::new(); + handle.read_line(&mut amount)?; + let amount = amount.trim(); + println!("The amount you wanted to deposit: {}\n", &amount); + + println!("Please input the pin:"); + let mut pin = String::new(); + handle.read_line(&mut pin)?; + let pin = pin.trim(); + + database::deposit(amount, pin, account_number) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + } else if query == "0" { + database::show_balance(account_number) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + } else { + eprintln!("Invalid choice. Please try again..."); + } + } + Ok(()) +} \ No newline at end of file From c448a21861fea6f99200254a98cd603887a04d4e Mon Sep 17 00:00:00 2001 From: Akshar Goyal Date: Thu, 23 Oct 2025 11:23:01 +0000 Subject: [PATCH 2/6] chore(banking-system): update readme and toml --- .../MiniProjects/banking-system/Cargo.toml | 8 +- .../cli/MiniProjects/banking-system/README.md | 128 ++++++++++++------ 2 files changed, 97 insertions(+), 39 deletions(-) diff --git a/Domains/cli/MiniProjects/banking-system/Cargo.toml b/Domains/cli/MiniProjects/banking-system/Cargo.toml index 301de984..811ece4b 100644 --- a/Domains/cli/MiniProjects/banking-system/Cargo.toml +++ b/Domains/cli/MiniProjects/banking-system/Cargo.toml @@ -2,8 +2,14 @@ name = "banking-system" version = "0.1.0" edition = "2024" +description = "A small CLI banking system (account creation, login, deletion) using SQLite persistence." +authors = ["AksharGoyal"] +license = "MIT" +readme = "README.md" +keywords = ["cli", "banking", "rust", "sqlite"] +categories = ["command-line-utilities"] [dependencies] clap = { version = "4.5.50", features = ["derive"] } rand = "0.9.2" -rusqlite = { version = "0.37.0", features = ["backup"] } +rusqlite = { version = "0.37.0", features = ["backup"] } \ No newline at end of file diff --git a/Domains/cli/MiniProjects/banking-system/README.md b/Domains/cli/MiniProjects/banking-system/README.md index 98c9de7d..db121b47 100644 --- a/Domains/cli/MiniProjects/banking-system/README.md +++ b/Domains/cli/MiniProjects/banking-system/README.md @@ -1,56 +1,108 @@ -# Simple Banking App +# Simple Banking App (banking-system) + +A small CLI banking system written in Rust for learning and demo purposes. +Provides account creation, login, simple operations and account deletion via a single binary. **Contributor:** AksharGoyal -## Overview -A banking system application built in Rust. +--- + +## Features +- Create accounts with randomly generated 10-digit account numbers and 6-digit PINs +- Login using account number + PIN +- Delete accounts +- Persistent storage using SQLite (via rusqlite) so accounts survive between runs +- CLI parsing and subcommands handled with clap for a familiar UX and easy extension +- Random data generation uses rand for account numbers and PINs -## Tech stack -- Rust +Dependency mapping (from Cargo.toml): +- clap — command-line parsing and subcommands +- rand — secure-ish random generation for account numbers and PINs +- rusqlite — persistent SQLite backend (database file is created in the working directory by default) ## Prerequisites -- Rust +- Rust toolchain (rustc + cargo) installed + - Install from https://rustup.rs if needed -## Quickstart -1. Make sure you are in banking-system directory. Then run -```sh -cargo install --path . -``` +## Quickstart -That's it! You can now use the command `banking-system` command in your cli to do simple bank-related operations. +1. Change into the project directory: + ```sh + cd ./Domains/cli/MiniProjects/banking-system # Assuming you are in ProjectHive repo + ``` -2. You can also add your own tests and check them: -```sh -cargo test -- --test-threads=1 -``` +2. Install the CLI locally: + ```sh + cargo install --path . + ``` + This installs a `banking-system` binary into your cargo bin directory (usually ~/.cargo/bin). -## banking-system subcommands +3. Run commands directly (either with the installed binary or via cargo): + ```sh + # using installed binary + banking-system create -You can run the following subcommands (ex: create) in 2 ways. -```sh -banking-system create # or -cargo run -- create -``` + # or using cargo (no install required) + cargo run -- create + ``` -- create — Creates a new account with randomly generated account number and PIN -- login — Logins to an existing account to allow user to do banking operations -- delete - Deletes the existing account +## Commands / Usage -## Examples +- Create a new account + ```sh + banking-system create + ``` + Example output: + ``` + YOUR NEW ACCOUNT: 6046334535 + YOUR PIN: 688154 + ``` -Create an account: -```sh -banking-system create ->>> YOUR NEW ACCOUNT: `6046334535` ->>> YOUR PIN: `688154` -``` +- Login to an existing account + ```sh + banking-system login + ``` + Example: + ```sh + banking-system login 6046334535 688154 + ``` -Login into an existing account: -```sh -banking-system login 6046334535 688154 -``` +- Delete an existing account + ```sh + banking-system delete + ``` + Example output: + ``` + banking-system delete 6046334535 688154 + >>> DELETED ACCOUNT: 6046334535 + ``` + +Notes: +- You can run any command via `cargo run -- ...` while developing. +- Keep your generated account numbers and PINs safe for subsequent operations. -Delete an existing account +## Testing +Run the unit tests: ```sh -banking-system delete 6046334535 688154 +cargo test -- --test-threads=1 ``` +(Tests are run single-threaded to avoid concurrency issues with any shared test state.) + +## Development tips +- Format code with: + ```sh + cargo fmt + ``` +- Check for common issues: + ```sh + cargo clippy -- -D warnings + ``` +- To iterate quickly without installing: + ```sh + cargo run -- + ``` +- To uninstall the app & clean everything: + ```sh + cargo uninstall banking-system + cargo clean + ``` From 0d57453b53aa50dbc01c5fbd5261e12799b8fdf3 Mon Sep 17 00:00:00 2001 From: Akshar Goyal Date: Thu, 23 Oct 2025 11:30:25 +0000 Subject: [PATCH 3/6] chore(banking-system): run cargo fmt --- Domains/cli/MiniProjects/banking-system/src/cli.rs | 2 +- Domains/cli/MiniProjects/banking-system/src/database.rs | 4 ++-- Domains/cli/MiniProjects/banking-system/src/lib.rs | 2 +- Domains/cli/MiniProjects/banking-system/src/main.rs | 2 +- Domains/cli/MiniProjects/banking-system/src/menu.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Domains/cli/MiniProjects/banking-system/src/cli.rs b/Domains/cli/MiniProjects/banking-system/src/cli.rs index bf9dd512..c8949487 100644 --- a/Domains/cli/MiniProjects/banking-system/src/cli.rs +++ b/Domains/cli/MiniProjects/banking-system/src/cli.rs @@ -32,4 +32,4 @@ pub enum AccountOpts { /// Create new account. This will have a randomly generated PIN. #[command(name = "create")] Create, -} \ No newline at end of file +} diff --git a/Domains/cli/MiniProjects/banking-system/src/database.rs b/Domains/cli/MiniProjects/banking-system/src/database.rs index 54591356..5444886f 100644 --- a/Domains/cli/MiniProjects/banking-system/src/database.rs +++ b/Domains/cli/MiniProjects/banking-system/src/database.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use crate::luhn::AccountNumber; -use rand::rng; use rand::prelude::*; +use rand::rng; use rusqlite::{Connection, Result}; #[derive(Debug)] @@ -287,4 +287,4 @@ pub fn show_balance(account_number: &str) -> Result<()> { &account_number, &amount_from_db ); Ok(()) -} \ No newline at end of file +} diff --git a/Domains/cli/MiniProjects/banking-system/src/lib.rs b/Domains/cli/MiniProjects/banking-system/src/lib.rs index b4262942..60dc900d 100644 --- a/Domains/cli/MiniProjects/banking-system/src/lib.rs +++ b/Domains/cli/MiniProjects/banking-system/src/lib.rs @@ -3,4 +3,4 @@ pub mod cli; pub mod database; pub mod luhn; -pub mod menu; \ No newline at end of file +pub mod menu; diff --git a/Domains/cli/MiniProjects/banking-system/src/main.rs b/Domains/cli/MiniProjects/banking-system/src/main.rs index 68b8613a..afb6d289 100644 --- a/Domains/cli/MiniProjects/banking-system/src/main.rs +++ b/Domains/cli/MiniProjects/banking-system/src/main.rs @@ -67,4 +67,4 @@ fn main() -> Result<()> { } }; Ok(()) -} \ No newline at end of file +} diff --git a/Domains/cli/MiniProjects/banking-system/src/menu.rs b/Domains/cli/MiniProjects/banking-system/src/menu.rs index f6306f61..74cb71a5 100644 --- a/Domains/cli/MiniProjects/banking-system/src/menu.rs +++ b/Domains/cli/MiniProjects/banking-system/src/menu.rs @@ -93,4 +93,4 @@ pub fn prompt(account_number: &str) -> std::io::Result<()> { } } Ok(()) -} \ No newline at end of file +} From c4072a6b5bd6f530780f97a1182b5f405f424038 Mon Sep 17 00:00:00 2001 From: Akshar Goyal Date: Thu, 23 Oct 2025 11:35:31 +0000 Subject: [PATCH 4/6] chore(banking-system): separate luhn tests into tests folder --- .../MiniProjects/banking-system/src/luhn.rs | 66 +------------------ .../banking-system/tests/luhn_tests.rs | 61 +++++++++++++++++ 2 files changed, 62 insertions(+), 65 deletions(-) create mode 100644 Domains/cli/MiniProjects/banking-system/tests/luhn_tests.rs diff --git a/Domains/cli/MiniProjects/banking-system/src/luhn.rs b/Domains/cli/MiniProjects/banking-system/src/luhn.rs index ec39943c..14117a5e 100644 --- a/Domains/cli/MiniProjects/banking-system/src/luhn.rs +++ b/Domains/cli/MiniProjects/banking-system/src/luhn.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Unlicense -use rand::rng; use rand::prelude::*; +use rand::rng; use std::fmt::Display; use std::str::FromStr; @@ -130,67 +130,3 @@ fn get_check_digit(payload: &[u8]) -> u8 { 10 - (luhn_sum % 10) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - /// Valid account numbers in different lengths - fn valid_account_numbers_in_different_lengths() { - let accounts = [ - AccountNumber::from_str("35001576202"), - AccountNumber::from_str("8536276945"), - AccountNumber::from_str("20024"), - AccountNumber::from_str("26"), - ]; - - for account in accounts { - assert!(account.is_ok()); - } - } - - #[test] - fn valid_account_numbers_fixed_length() { - let valid_account_numbers = [ - "2334841596", - "5072686164", - "8330789085", - "2303133926", - "7730632457", - "1310866767", - "9083062142", - "8936042657", - "3188178648", - "1513312791", - "0204434294", - ]; - - for v in valid_account_numbers { - let account = AccountNumber::from_str(v); - assert!(account.is_ok()); - } - } - - #[test] - fn invalid_account_numbers_fixed_length() { - let invalid_account_numbers = [ - "2334841592", - "5072686163", - "8330789084", - "2303133925", - "7730632456", - "1310866766", - "9083062141", - "8936042656", - "3188178647", - "1513312790", - "0204434293", - ]; - - for v in invalid_account_numbers { - let account = AccountNumber::from_str(v); - assert!(account.is_err()); - } - } -} \ No newline at end of file diff --git a/Domains/cli/MiniProjects/banking-system/tests/luhn_tests.rs b/Domains/cli/MiniProjects/banking-system/tests/luhn_tests.rs new file mode 100644 index 00000000..8496c897 --- /dev/null +++ b/Domains/cli/MiniProjects/banking-system/tests/luhn_tests.rs @@ -0,0 +1,61 @@ +use banking_system::luhn::AccountNumber; +use std::str::FromStr; + +#[test] +/// Valid account numbers in different lengths +fn valid_account_numbers_in_different_lengths() { + let accounts = [ + AccountNumber::from_str("35001576202"), + AccountNumber::from_str("8536276945"), + AccountNumber::from_str("20024"), + AccountNumber::from_str("26"), + ]; + + for account in accounts { + assert!(account.is_ok()); + } +} + +#[test] +fn valid_account_numbers_fixed_length() { + let valid_account_numbers = [ + "2334841596", + "5072686164", + "8330789085", + "2303133926", + "7730632457", + "1310866767", + "9083062142", + "8936042657", + "3188178648", + "1513312791", + "0204434294", + ]; + + for v in valid_account_numbers { + let account = AccountNumber::from_str(v); + assert!(account.is_ok()); + } +} + +#[test] +fn invalid_account_numbers_fixed_length() { + let invalid_account_numbers = [ + "2334841592", + "5072686163", + "8330789084", + "2303133925", + "7730632456", + "1310866766", + "9083062141", + "8936042656", + "3188178647", + "1513312790", + "0204434293", + ]; + + for v in invalid_account_numbers { + let account = AccountNumber::from_str(v); + assert!(account.is_err()); + } +} From 6fd54ffa9ad498bf6da693199e22729f3253a50b Mon Sep 17 00:00:00 2001 From: Akshar Goyal Date: Thu, 23 Oct 2025 11:39:12 +0000 Subject: [PATCH 5/6] chore(banking-system): include tech stack in readme --- Domains/cli/MiniProjects/banking-system/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Domains/cli/MiniProjects/banking-system/README.md b/Domains/cli/MiniProjects/banking-system/README.md index db121b47..cf44c0f5 100644 --- a/Domains/cli/MiniProjects/banking-system/README.md +++ b/Domains/cli/MiniProjects/banking-system/README.md @@ -24,6 +24,17 @@ Dependency mapping (from Cargo.toml): - Rust toolchain (rustc + cargo) installed - Install from https://rustup.rs if needed +## Tech stack + +- Language: Rust +- Build / package manager: cargo +- CLI parsing: clap +- Random generation: rand +- Persistence: rusqlite +- Testing: cargo test +- Formatting: rustfmt (cargo fmt) +- Linting: clippy (cargo clippy) + ## Quickstart 1. Change into the project directory: From 53a1532a6312e7216c9cae0094389bb3e5a72834 Mon Sep 17 00:00:00 2001 From: Akshar Goyal Date: Thu, 23 Oct 2025 11:40:33 +0000 Subject: [PATCH 6/6] chore(banking-system): minor change in readme --- Domains/cli/MiniProjects/banking-system/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Domains/cli/MiniProjects/banking-system/README.md b/Domains/cli/MiniProjects/banking-system/README.md index cf44c0f5..23f44f13 100644 --- a/Domains/cli/MiniProjects/banking-system/README.md +++ b/Domains/cli/MiniProjects/banking-system/README.md @@ -1,4 +1,4 @@ -# Simple Banking App (banking-system) +# Simple Banking App (rust) A small CLI banking system written in Rust for learning and demo purposes. Provides account creation, login, simple operations and account deletion via a single binary. @@ -25,7 +25,6 @@ Dependency mapping (from Cargo.toml): - Install from https://rustup.rs if needed ## Tech stack - - Language: Rust - Build / package manager: cargo - CLI parsing: clap @@ -36,7 +35,6 @@ Dependency mapping (from Cargo.toml): - Linting: clippy (cargo clippy) ## Quickstart - 1. Change into the project directory: ```sh cd ./Domains/cli/MiniProjects/banking-system # Assuming you are in ProjectHive repo