From d01c7ebda5031652f81bebaf0a1c614373ca507e Mon Sep 17 00:00:00 2001 From: Bikash Giri Date: Wed, 21 May 2025 16:37:41 -0400 Subject: [PATCH 01/12] Initial front end commit. Please do check readme file first. --- .DS_Store | Bin 0 -> 6148 bytes src/.DS_Store | Bin 0 -> 6148 bytes src/README.md | 119 +++++++++++++++++++++++++++++++ src/app.py | 25 +++++++ src/static/script.js | 57 +++++++++++++++ src/static/style.css | 154 ++++++++++++++++++++++++++++++++++++++++ src/templates/chat.html | 52 ++++++++++++++ 7 files changed, 407 insertions(+) create mode 100644 .DS_Store create mode 100644 src/.DS_Store create mode 100644 src/README.md create mode 100644 src/app.py create mode 100644 src/static/script.js create mode 100644 src/static/style.css create mode 100644 src/templates/chat.html diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a37fd1a3688ea1e4bd54c38a003ed05d4bc82cf9 GIT binary patch literal 6148 zcmeHK!EVz)5S>i|u~i}E08%eVD{+lV8cGoA#iY)mQp=%6Z~zqIIBE^HH;NN{h$8t6 zd;5Gp&e=Vjd$jav(J_{3jm_Oh;{*50N|h#)?6HZW0WV~ zuo}-0Pv{;`A%MYQqQ+seQEeywMFr^DHQ@*j;Q$73>G`P;GxS=Vqwjpzh7o`ZCs_Fe zd^kqjffOS1#4ColdxcSl9A+?Hufh)NeYq+u@@bgpUSGm#66fQ zZeS9Q>$t<#_Hx;M^sp@-^t!9ITt4o0+H$YAw_3T*oqPB9PloT3G*cf)27Kl5o!54Y zv4W2n@M@CN-C(TJX_fkwNtN&OI-R$lKNY=iUr7<>r7HSEc6xSQ*Z09JOH_7>ovL4V z>DD&13RnfK0;~XkA)GC%h*iKUU=_Hc0DT`MI-$o{8q}W-9P|+Yv4PXtFs46^#F301 zV`&gGXu?nt4OQ?FLl`>FORn=6OM`|Ef)5{p?=1L)BJ|y{ztrU*JcD+$3Rnf!6{wrb z7VZCo>(Bq|Bzt8QunPQF3W$au_i2o8Ws(nPxvNgSG#3RRVI z1tfljBY(graOMXPp8zwqE1EWxJt4Gss_}E|H+J(Tv10&WgGsOl&;kGhov?8piyEVT z@-^F1Efmbe7#X~|AM=qPbAOGk8?*vifqzW_dUu=PL59B$`ttireRt72KZJ?DZo&~9 zVygoW5Ic}Su=K`L#K)D0Cgf1y35F+K-lPC@NbigNR zkc(JXb{M8%4qKOJv;BVC>h=2bwl(Ybx^3&${=s~18ker!ynQr$87C=!DN_3xi40hKWl9{iD$oot@xwL4B@o*v`a z^xsWPPKc?@AQp)VAzw+iL}R#-~J z9yBIH5oIV-M+_#zvE5QUTVW|th67WF52j{j>V(3?>^Qz9=)i1=y3`721*!^c%gYw+ z|A)Un|5t πŸ” Return both `reply` and `intent` + +### πŸ”Ή Plug in Gemini API + +Use the Gemini API to: +- Detect user intent +- Generate a context-aware reply + +Then return both values to the frontend. + +--- + +## πŸ§ͺ Running the App Locally + +### 1. Install Flask (once) +```bash +pip install flask +``` + +### 2. Start the server +```bash +python app.py +``` + +### 3. Visit the app +Open browser: [http://127.0.0.1:5000](http://127.0.0.1:5000) + +--- + +## πŸ™‹ Front-End Contact: Bikash +Handles HTML, CSS, JS, and integration layout. diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..f590a2a --- /dev/null +++ b/src/app.py @@ -0,0 +1,25 @@ +from flask import Flask, render_template, request, jsonify + +app = Flask(__name__) + +@app.route("/") +def index(): + return render_template("chat.html") + +@app.route("/chat", methods=["POST"]) +def chat(): + user_input = request.json.get("message") + # Dummy logic for demo + if "deposit" in user_input or "withdraw" in user_input: + return jsonify({"reply": "Please login to continue."}) + return jsonify({"reply": f"You said: {user_input}"}) + +@app.route("/auth", methods=["POST"]) +def auth(): + data = request.json + if data["username"] == "admin" and data["password"] == "123": + return jsonify({"status": "success"}) + return jsonify({"status": "fail"}) + +if __name__ == "__main__": + app.run(debug=True) diff --git a/src/static/script.js b/src/static/script.js new file mode 100644 index 0000000..1629ab4 --- /dev/null +++ b/src/static/script.js @@ -0,0 +1,57 @@ +let isAuthenticated = false; + +function toggleChat() { + const chatPopup = document.getElementById("chat-popup"); + chatPopup.style.display = (chatPopup.style.display === "flex") ? "none" : "flex"; +} + +function appendMessage(sender, msg) { + const chatBox = document.getElementById("chat-box"); + const message = document.createElement("p"); + message.innerHTML = `${sender}: ${msg}`; + chatBox.appendChild(message); + chatBox.scrollTop = chatBox.scrollHeight; +} + +async function sendMessage() { + const input = document.getElementById("message"); + const message = input.value; + if (!message.trim()) return; + input.value = ""; + + appendMessage("You", message); + + const res = await fetch("/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message: message }) + }); + + const data = await res.json(); + appendMessage("Bot", data.reply); + + if (data.reply.toLowerCase().includes("please login")) { + document.getElementById("login-modal").style.display = "flex"; + } +} + +async function submitLogin() { + const username = document.getElementById("username").value; + const password = document.getElementById("password").value; + + const res = await fetch("/auth", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }) + }); + + const data = await res.json(); + + if (data.status === "success") { + isAuthenticated = true; + document.getElementById("login-modal").style.display = "none"; + appendMessage("System", "βœ… Login successful! You can now proceed."); + } else { + appendMessage("System", "❌ Login failed. Try again."); + } +} diff --git a/src/static/style.css b/src/static/style.css new file mode 100644 index 0000000..6a33756 --- /dev/null +++ b/src/static/style.css @@ -0,0 +1,154 @@ +body { + font-family: 'Segoe UI', sans-serif; + background: #f5f5f5; + margin: 0; + padding: 0; + } + + /* Floating button */ + #chat-toggle { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #007bff; + color: white; + border-radius: 50%; + width: 60px; + height: 60px; + font-size: 28px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + box-shadow: 0 4px 10px rgba(0,0,0,0.3); + z-index: 1000; + } + + /* Chat popup window */ + .chat-popup { + display: none; + flex-direction: column; + position: fixed; + bottom: 100px; + right: 20px; + width: 350px; + height: 450px; + background: white; + border-radius: 12px; + box-shadow: 0 6px 20px rgba(0,0,0,0.3); + overflow: hidden; + z-index: 999; + } + + .chat-header { + background: #007bff; + color: white; + padding: 12px; + display: flex; + justify-content: space-between; + align-items: center; + } + + .chat-header h3 { + margin: 0; + font-size: 16px; + } + + .chat-box { + flex-grow: 1; + padding: 15px; + overflow-y: auto; + font-size: 14px; + background-color: #f9f9f9; + } + + .input-area { + display: flex; + border-top: 1px solid #ddd; + } + + .input-area input { + flex: 1; + padding: 12px; + border: none; + font-size: 14px; + } + + .input-area button { + background: #007bff; + border: none; + padding: 12px 16px; + color: white; + font-size: 14px; + cursor: pointer; + } + + /* Login Modal */ + .modal { + display: none; + position: fixed; + top: 0; left: 0; + width: 100%; height: 100%; + background-color: rgba(0, 0, 0, 0.6); + justify-content: center; + align-items: center; + z-index: 2000; + } + + .modal-content { + background: white; + padding: 30px; + border-radius: 8px; + text-align: center; + min-width: 300px; + } + + .modal-content input { + width: 90%; + padding: 10px; + margin: 10px 0; + } + + .modal-content button { + padding: 10px 20px; + background: #007bff; + color: white; + border: none; + cursor: pointer; + } + + /* Homepage Welcome Content */ +.homepage-content { + text-align: center; + margin: 60px auto 100px; + max-width: 700px; + padding: 20px; + background: #fff; + border-radius: 12px; + box-shadow: 0 6px 20px rgba(0,0,0,0.05); + } + + .homepage-content h1 { + color: #007bff; + font-size: 2em; + } + + .homepage-content p { + color: #555; + font-size: 1.1em; + margin-top: 10px; + } + + .homepage-content ul { + margin-top: 20px; + text-align: left; + display: inline-block; + color: #333; + } + + .homepage-content ul li { + margin-bottom: 10px; + font-size: 1em; + } + + \ No newline at end of file diff --git a/src/templates/chat.html b/src/templates/chat.html new file mode 100644 index 0000000..5974c59 --- /dev/null +++ b/src/templates/chat.html @@ -0,0 +1,52 @@ + + + + + GRP7 Digital Banking Bot + + + + + + +
+

Welcome to GRP7 Digital Banking Bot

+

Need help with your banking? Talk to our GRP7 Banking Assistant β€” your 24/7 digital banking support system.

+
    +
  • Check FAQs instantly
  • +
  • Securely log in to deposit or withdraw money
  • +
  • Get friendly and helpful banking support
  • +
+
+ + +
+ +
+ + +
+
+

πŸ’¬ GRP7 Assistant

+ +
+
+
+ + +
+
+ + + + + + + From f9718a10f9049414961c2dacc8d82509ba979812 Mon Sep 17 00:00:00 2001 From: omisdami Date: Wed, 21 May 2025 22:00:01 -0400 Subject: [PATCH 02/12] src to chatbot --- {src => chatbot }/.DS_Store | Bin {src => chatbot }/README.md | 0 {src => chatbot }/app.py | 0 {src => chatbot }/static/script.js | 0 {src => chatbot }/static/style.css | 0 {src => chatbot }/templates/chat.html | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {src => chatbot }/.DS_Store (100%) rename {src => chatbot }/README.md (100%) rename {src => chatbot }/app.py (100%) rename {src => chatbot }/static/script.js (100%) rename {src => chatbot }/static/style.css (100%) rename {src => chatbot }/templates/chat.html (100%) diff --git a/src/.DS_Store b/chatbot /.DS_Store similarity index 100% rename from src/.DS_Store rename to chatbot /.DS_Store diff --git a/src/README.md b/chatbot /README.md similarity index 100% rename from src/README.md rename to chatbot /README.md diff --git a/src/app.py b/chatbot /app.py similarity index 100% rename from src/app.py rename to chatbot /app.py diff --git a/src/static/script.js b/chatbot /static/script.js similarity index 100% rename from src/static/script.js rename to chatbot /static/script.js diff --git a/src/static/style.css b/chatbot /static/style.css similarity index 100% rename from src/static/style.css rename to chatbot /static/style.css diff --git a/src/templates/chat.html b/chatbot /templates/chat.html similarity index 100% rename from src/templates/chat.html rename to chatbot /templates/chat.html From 4dfe3529819ce1fb5bf34d6076c8329b3f7d3f90 Mon Sep 17 00:00:00 2001 From: omisdami Date: Fri, 23 May 2025 13:55:25 -0400 Subject: [PATCH 03/12] Authentication added --- .DS_Store | Bin 6148 -> 6148 bytes .gitignore | 5 + LICENSE | 21 --- README.md | 7 - chatbot /app.py | 25 --- {chatbot => chatbot}/.DS_Store | Bin {chatbot => chatbot}/README.md | 0 chatbot/__init__.py | 0 chatbot/account.py | 72 ++++++++ chatbot/app.py | 71 +++++++ chatbot/bank.db | Bin 0 -> 28672 bytes chatbot/database.py | 214 ++++++++++++++++++++++ chatbot/init.sql | 44 +++++ chatbot/models.py | 46 +++++ {chatbot => chatbot}/static/script.js | 70 +++---- {chatbot => chatbot}/static/style.css | 0 {chatbot => chatbot}/templates/chat.html | 10 +- 17 files changed, 487 insertions(+), 98 deletions(-) create mode 100644 .gitignore delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 chatbot /app.py rename {chatbot => chatbot}/.DS_Store (100%) rename {chatbot => chatbot}/README.md (100%) create mode 100644 chatbot/__init__.py create mode 100644 chatbot/account.py create mode 100644 chatbot/app.py create mode 100644 chatbot/bank.db create mode 100644 chatbot/database.py create mode 100644 chatbot/init.sql create mode 100644 chatbot/models.py rename {chatbot => chatbot}/static/script.js (52%) rename {chatbot => chatbot}/static/style.css (100%) rename {chatbot => chatbot}/templates/chat.html (86%) diff --git a/.DS_Store b/.DS_Store index a37fd1a3688ea1e4bd54c38a003ed05d4bc82cf9..88610f9465770603c36588a3ca589d7d6a538069 100644 GIT binary patch delta 119 zcmZoMXfc@J&&awlU^gQp>tr6LWJ4B)GKN%!JccsQoc!dZoctsP1_l8J2F7k6t@j@c zfGh?ExVp)6m>Px640IF>jZA8F6sj$afNV2Ui^&Ge#+-0fn{P4evP^8q+RV=JmmdI# Cksx6J delta 230 zcmZoMXfc@J&&atkU^gQp=VTtHWL;*4Vum7yWY3)Z list[Account]: + """ + List all of the user's accoutns. + + :param user_id: The user ID of the account owner. + :return: All accounts that are avaialbe for transfering. + """ + return chatbot.database.load_accounts(user_id) + + +def list_transfer_target_accounts(user_id: str, + from_account: str) -> list[Account]: + """ + List all of the user's accounts that the specified account can transfer to. + + :param user_id: The user ID of the account owner. + :param from_account: The account number or account name that the fund will be transfered from. + :return: All the accounts that funds can be transfered from the specified account. + """ + return chatbot.database.load_transfer_target_accounts(user_id, from_account) + + +def transfer_between_accounts(user_id: str, + from_account: str, to_account: str, + amount: Decimal, description: str=""): + """ Transfer specific amount of fund from one account to the other of the same owner. + + :param user_id: The user ID of the account owner. + :param from_account: The account number or account name that the fund will be transfered from. + :param to_account: The account number or account name that the fund will be transfered to. + """ + chatbot.database.transfer_fund(user_id, from_account, to_account, amount, + description) + + +def list_transactions(user_id: str, account: str, + days: int) -> list[Transaction]: + """ + List all the transactions of the specified account. + + :param user_id: The user ID of the owner of the account. + :param account: The number or name of the account. + :param days: How many days in the past that the transactions need to be queried. + :return: The transactions that match conditions set by the arguments. + """ + to_date = date.today() + from_date = to_date - timedelta(days=days) + return chatbot.database.load_transactions(user_id, account, + from_date, to_date) + + +def withdraw(user_id: str, account: str, amount: Decimal, description: str=""): + transfer_between_accounts(user_id, + account, + chatbot.database.ACCOUNT_WITHDRAW_TO, + amount, description) + + +def deposit(user_id: str, account: str, amount: Decimal, description: str=""): + transfer_between_accounts(user_id, + chatbot.database.ACCOUNT_DEPOSIT_FROM, + account, + amount, + description) \ No newline at end of file diff --git a/chatbot/app.py b/chatbot/app.py new file mode 100644 index 0000000..f2b9259 --- /dev/null +++ b/chatbot/app.py @@ -0,0 +1,71 @@ +import os +from flask import Flask, render_template, request, jsonify, abort +import jwt +from datetime import datetime, timedelta +from database import auth_user, init_db + +# Initialize Flask app pointing to local templates/ and static/ +app = Flask(__name__, template_folder="templates", static_folder="static") + +# JWT configuration +SECRET_KEY = os.getenv("SECRET_KEY", "your_secret_key") +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 15 + + +# Helpers for JWT +def create_access_token(username: str) -> str: + expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + payload = {"sub": username, "exp": expire} + return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM) + + +def verify_access_token(token: str) -> str: + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + user = payload.get("sub") + if not user: + abort(401, "Invalid token payload") + return user + except jwt.ExpiredSignatureError: + abort(401, "Token has expired") + except jwt.InvalidTokenError: + abort(401, "Invalid token") + +# Routes +@app.route("/", methods=["GET"]) +def index(): + return render_template("chat.html") + +@app.route("/auth/login", methods=["POST"]) +def auth_login(): + data = request.get_json() or {} + username = data.get("username") + password = data.get("password") + if not username or not password: + abort(400, 'Missing "username" or "password"') + + # Validate against real database + if not auth_user(username, password): + return jsonify({"status": "fail"}), 401 + + token = create_access_token(username) + return jsonify({"status": "success", "access_token": token, "token_type": "bearer"}), 200 + +@app.route("/chat", methods=["POST"]) +def chat(): + auth_header = request.headers.get("Authorization", "") + if not auth_header.startswith("Bearer "): + return jsonify({"reply": "πŸ”’ Please login to continue."}), 401 + token = auth_header.split(" ", 1)[1] + user = verify_access_token(token) + + msg = request.json.get("message", "") + if "transfer" in msg.lower(): + return jsonify({"reply": f"πŸ”„ Transfer flow would run here for {user}."}) + if "faq" in msg.lower(): + return jsonify({"reply": "Here’s an FAQ answer stub."}) + return jsonify({"reply": "You are being transferred to a human agent."}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/chatbot/bank.db b/chatbot/bank.db new file mode 100644 index 0000000000000000000000000000000000000000..649d4beb60b888e34336ad1125cd20a890756829 GIT binary patch literal 28672 zcmeI)&uiOO00(f%vgKdcZy^UUO4u{SYfM5LIZNCg3Uw5Bn50hXsEk6vD7M;&ByuXr zOWRx680-Fm(bI0b40apz4;VWS3WHs9-en9{*k$iYmMz)N+GFXy0!4byU%ww8DFHnv z*}J=Kc_w|>ca9B@t_mLrqA1*=R1ky|+tX|hH;J7j!vZ^sk@JgAQ^HmIWr4gF=wgmWD_Vo&ebb?ow(i|&w3`}j?da@$dwZGX>fLUCV0)ts zi&5Qt;_+iNx8og{&O~@tE;5i!!!vd3*qqs3B&R<;F|~egcCLQRhsQ@V-e%2X!#FbR zu33NR`BU}7vvS%_Yfo?2n=PI0ZgnD~aD(=D+s!-m_I$a7Zv}O8_vQFkIC4r7S{T=S!}{n7n(*i`6TbTrqk?& z7jo9^6Silt+h+ESIK0rqMrK~(f#aBV_o;tRO^qp6xt5d_ZQ&xOaC#jzJWHMB#I&-s zB%W>gJbV_KWA;qjvy3BmqL_$bO}yxvjTC%<@SAMXYS(bx)4tOSCJ?4_m757!xw~}H z=qLK?s1p_ZSw6#mt`^uXkzWMz2l<^Hut5L<5P$##AOHafKmY;|fB*y_@a_uCNs{uB zXS&|%30p7dpE|wOxx}!l%B%PiLUm3YR;gi?nqza4$hkoNW-9>a@9qRKG6+Bb0uX=z z1Rwwb2tWV=5P$##{!<_;DWbnLAcbeuL`G7wyiVnx|L2SQ0{M;nNWLMD$SrcI_`3LO z@rUAbR*ek;5P$##AOHafKmY;|fB*y_@RkDQyj)T)C+2UgsMXa^*4As+{fANhoAZ`O zHN)wNe`)p2QjWzwl;*FGa(-;j_}sD|iC=xkBA2r)vLMaZMwy_c1M~4<6e?$e?pQy= zmUyT-)m=L1j*lnE`LS4ca?qVWWMj{{HxytwKNjmw3c3rG zZma|?#k%`AA(t+dGxICqR%L&Wvv1gk8*HbW=1JeRJVtdzWVtJmoEpu2W_bra$2es{ z{`r5NJP^pAou>k-4AJ6~q@dd`z YAOHafKmY;|fB*y_009U<00K bool: + """ + Return True if credentials match a record in UserCredentials. + """ + sql = """ + SELECT UserId + FROM UserCredentials + WHERE UserId = :user_id + AND Password = :password + """ + con = sqlite3.connect(DB_FILE) + cur = con.cursor() + cur.execute(sql, {"user_id": user_id, "password": password}) + authenticated = cur.fetchone() is not None + con.close() + return authenticated + + +def load_accounts(user_id: str) -> list[Account]: + """ + Return all Account objects for the given user_id. + """ + sql = """ + SELECT AccountNumber, AccountName, Balance, CurrencyCode + FROM Accounts + WHERE UserId = :user_id + """ + con = sqlite3.connect(DB_FILE) + cur = con.cursor() + cur.execute(sql, {"user_id": user_id}) + rows = cur.fetchall() + con.close() + + return [ + Account( + account_number=row[0], + account_name=row[1], + balance=row[2], + currency_code=row[3] + ) + for row in rows + ] + + +def load_transfer_target_accounts(user_id: str, from_account: str) -> list[Account]: + """ + Return all accounts for this user except the `from_account`. + """ + sql_num = """ + SELECT AccountNumber + FROM Accounts + WHERE UserId = :user_id + AND AccountName = :account_name + """ + sql_all = """ + SELECT AccountNumber, AccountName, Balance, CurrencyCode + FROM Accounts + WHERE UserId = :user_id + AND AccountNumber <> :account_number + """ + + con = sqlite3.connect(DB_FILE) + cur = con.cursor() + + cur.execute(sql_num, {"user_id": user_id, "account_name": from_account}) + found = cur.fetchone() + acct_num = found[0] if found else from_account + + cur.execute(sql_all, {"user_id": user_id, "account_number": acct_num}) + rows = cur.fetchall() + con.close() + + return [ + Account( + account_number=r[0], + account_name=r[1], + balance=r[2], + currency_code=r[3] + ) + for r in rows + ] + + +def transfer_fund(user_id: str, + from_account: str, + to_account: str, + amount: Decimal, + description: str = ""): + """ + Move `amount` from one account to another under the same user. + Records two transaction rows (debit + credit). + """ + # Resolve account numbers + sql_num = "SELECT AccountNumber FROM Accounts WHERE UserId=:user_id AND AccountName=:account_name" + con = sqlite3.connect(DB_FILE) + cur = con.cursor() + + def resolve(name_or_num): + cur.execute(sql_num, {"user_id": user_id, "account_name": name_or_num}) + r = cur.fetchone() + return r[0] if r else name_or_num + + from_num = resolve(from_account) + to_num = resolve(to_account) + + # Update balances + sql_update = "UPDATE Accounts SET Balance = Balance + :delta WHERE AccountNumber = :acct" + cur.execute(sql_update, {"delta": -amount, "acct": from_num}) + cur.execute(sql_update, {"delta": amount, "acct": to_num}) + + # Record transactions + txn_no = uuid.uuid4().int + now = datetime.now().isoformat() + entries = [ + (txn_no, from_num, to_num, now, -amount, description, "D"), + (txn_no, to_num, from_num, now, amount, description, "C") + ] + sql_txn = """ + INSERT INTO Transactions + (TransactionNumber, AccountNumber, OtherAccountNumber, + TransactionDateTime, Amount, Description, TransactionTypeCode) + VALUES (?, ?, ?, ?, ?, ?, ?) + """ + cur.executemany(sql_txn, entries) + con.commit() + con.close() + + +def load_transactions(user_id: str, + account: str, + from_date: date, + to_date: date) -> list[Transaction]: + """ + Return all Transaction objects for `account` between `from_date` and `to_date`. + """ + sql_num = "SELECT AccountNumber FROM Accounts WHERE UserId=:user_id AND AccountName=:account_name" + sql_txn = """ + SELECT TransactionNumber, AccountNumber, TransactionDateTime, + TransactionTypeCode, Amount, BalanceAfter, OtherAccountNumber, Description + FROM Transactions + WHERE AccountNumber = :acct + AND TransactionDateTime >= :from_time + AND TransactionDateTime < :before_time + """ + + before = to_date + timedelta(days=1) + from_ts = datetime.combine(from_date, datetime.min.time()).isoformat() + before_ts = datetime.combine(before, datetime.min.time()).isoformat() + + con = sqlite3.connect(DB_FILE) + cur = con.cursor() + cur.execute(sql_num, {"user_id": user_id, "account_name": account}) + row = cur.fetchone() + acct = row[0] if row else account + + cur.execute(sql_txn, { + "acct": acct, + "from_time": from_ts, + "before_time": before_ts + }) + rows = cur.fetchall() + con.close() + + def describe(other_acct): + if other_acct == ACCOUNT_WITHDRAW_TO: + return "Withdraw" + if other_acct == ACCOUNT_DEPOSIT_FROM: + return "Deposit" + return "Transfer" + + return [ + Transaction( + transaction_number = r[0], + account_number = r[1], + date_time = r[2], + transaction_type = "Credit" if r[3] == "C" else "Debit", + amount = r[4], + balance_after = r[5], + description = describe(r[6]) + (" " + r[7] if r[7] else "") + ) + for r in rows + ] + + +def init_db(): + """ + Create the database and seed it from init.sql if bank.db is missing. + """ + sql_path = Path(__file__).parent / "init.sql" + con = sqlite3.connect(DB_FILE) + cur = con.cursor() + cur.executescript(sql_path.read_text()) + con.commit() + con.close() + +# Auto-init if missing +if not DB_FILE.exists(): + init_db() diff --git a/chatbot/init.sql b/chatbot/init.sql new file mode 100644 index 0000000..4c97325 --- /dev/null +++ b/chatbot/init.sql @@ -0,0 +1,44 @@ +CREATE TABLE UserCredentials ( + UserId Text NOT NULL PRIMARY KEY, + Password Text NOT NULL +); + +CREATE TABLE Accounts ( + AccountNumber Text NOT NULL PRIMARY KEY, + UserId Text NOT NULL, + AccountName Text NOT NULL, + Balance NUMERIC NOT NULL, + CurrencyCode Text NOT NULL +); + +CREATE TABLE Transactions ( + TransactionNumber NUMERIC NOT NULL, + AccountNumber Text NOT NULL, + OtherAccountNumber Text NOT NULL, + TransactionDateTime Text NOT NULL, + TransactionTypeCode Text NOT NULL, + Amount NUMERIC NOT NULL, + BalanceAfter NUMERIC NOT NULL, + CONSTRAINT PK_Transactions PRIMARY KEY (TransactionNumber, AccountNumber), + FOREIGN KEY(AccountNumber) REFERENCES Accounts(AccountNumber), + FOREIGN KEY(OtherAccountNumber) REFERENCES Accounts(AccountNumber) +); + +INSERT INTO UserCredentials (UserId, Password) + VALUES ('test1', 'password1'), + ('test2', 'password2'), + ('test3', 'passowrd3'); + +INSERT INTO Accounts (AccountNumber, UserId, AccountName, Balance, CurrencyCode) + VALUES ('0000000001', 'thebank', 'Bank Withdraw', 0, 'CAD'), + ('0000000002', 'thebank', 'Bank Deposit', 0, 'CAD'), + ('1234567890', 'test1', 'Chequing', 100000, 'CAD'), + ('2345678901', 'test1', 'Saving', 100000, 'CAD'), + ('3456789012', 'test1', 'Credit Card', 500, 'CAD'), + ('4567890123', 'test2', 'Chequing', 100000, 'CAD'), + ('5678901234', 'test2', 'Saving', 100000, 'CAD'), + ('6789012345', 'test2', 'Credit Card', 500, 'CAD'), + ('7890123456', 'test3', 'Chequing', 100000, 'CAD'), + ('8901234567', 'test3', 'Saving', 100000, 'CAD'), + ('9012345678', 'test3', 'Credit Card', 500, 'CAD'); + \ No newline at end of file diff --git a/chatbot/models.py b/chatbot/models.py new file mode 100644 index 0000000..bf1f8e4 --- /dev/null +++ b/chatbot/models.py @@ -0,0 +1,46 @@ +from dataclasses import dataclass +from datetime import datetime +from decimal import Decimal + + +@dataclass +class Account: + """Represent an account of a user""" + + account_number: str + """The unique account number.""" + + account_name: str + """Name of the account.""" + + balance: Decimal + """The current balance of the account.""" + + currency_code: str + """The currency of the fund in the account""" + + +@dataclass +class Transaction: + """Represent one transaction of an account""" + + transaction_number: int + """A unique string identifyig the transaction. All records involved in one transaction share a transaction number""" + + account_number: str + """The account number of transaction being applied to.""" + + transaction_type: str + """The transaction can be credit or debit.""" + + date_time: datetime + """The date and time the transaction happens.""" + + amount: Decimal + """The amount involved in the transaction""" + + description: str + """Additional description of the transaction""" + + balance_after: Decimal + """The account balance after the transaction""" \ No newline at end of file diff --git a/chatbot /static/script.js b/chatbot/static/script.js similarity index 52% rename from chatbot /static/script.js rename to chatbot/static/script.js index 1629ab4..55e1eac 100644 --- a/chatbot /static/script.js +++ b/chatbot/static/script.js @@ -1,57 +1,47 @@ -let isAuthenticated = false; - -function toggleChat() { - const chatPopup = document.getElementById("chat-popup"); - chatPopup.style.display = (chatPopup.style.display === "flex") ? "none" : "flex"; -} - -function appendMessage(sender, msg) { - const chatBox = document.getElementById("chat-box"); - const message = document.createElement("p"); - message.innerHTML = `${sender}: ${msg}`; - chatBox.appendChild(message); - chatBox.scrollTop = chatBox.scrollHeight; -} - -async function sendMessage() { - const input = document.getElementById("message"); - const message = input.value; - if (!message.trim()) return; - input.value = ""; - - appendMessage("You", message); - - const res = await fetch("/chat", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message: message }) - }); - - const data = await res.json(); - appendMessage("Bot", data.reply); - - if (data.reply.toLowerCase().includes("please login")) { - document.getElementById("login-modal").style.display = "flex"; - } -} +let accessToken = null; async function submitLogin() { const username = document.getElementById("username").value; const password = document.getElementById("password").value; - const res = await fetch("/auth", { + const res = await fetch("/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }) }); const data = await res.json(); - - if (data.status === "success") { - isAuthenticated = true; + if (res.ok && data.status === "success") { + accessToken = data.access_token; document.getElementById("login-modal").style.display = "none"; appendMessage("System", "βœ… Login successful! You can now proceed."); } else { appendMessage("System", "❌ Login failed. Try again."); } } + +async function sendMessage() { + const input = document.getElementById("message"); + const message = input.value.trim(); + if (!message) return; + input.value = ""; + appendMessage("You", message); + + if (!accessToken) { + appendMessage("System", "πŸ”’ Please login to continue."); + document.getElementById("login-modal").style.display = "flex"; + return; + } + + const res = await fetch("/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${accessToken}` + }, + body: JSON.stringify({ message }) + }); + + const data = await res.json(); + appendMessage("Bot", data.reply); +} \ No newline at end of file diff --git a/chatbot /static/style.css b/chatbot/static/style.css similarity index 100% rename from chatbot /static/style.css rename to chatbot/static/style.css diff --git a/chatbot /templates/chat.html b/chatbot/templates/chat.html similarity index 86% rename from chatbot /templates/chat.html rename to chatbot/templates/chat.html index 5974c59..9b24db7 100644 --- a/chatbot /templates/chat.html +++ b/chatbot/templates/chat.html @@ -20,12 +20,12 @@

Welcome to GRP7 Digital Banking Bot

-
+
-
+