Spaces:
Running
Running
| # database.py β SQLite database: connection, seeding, and queries | |
| import json | |
| import sqlite3 | |
| import os | |
| from seed_data import CUSTOMERS, TRANSACTIONS | |
| DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "neobank.db") | |
| def get_connection() -> sqlite3.Connection: | |
| """Get a SQLite connection with row factory for dict-like access.""" | |
| conn = sqlite3.connect(DB_PATH, check_same_thread=False) | |
| conn.row_factory = sqlite3.Row | |
| return conn | |
| def init_database(conn: sqlite3.Connection): | |
| """Create tables and seed data. Safe to call on every app start.""" | |
| cur = conn.cursor() | |
| # ββ Create tables ββ | |
| cur.execute(""" | |
| CREATE TABLE IF NOT EXISTS customers ( | |
| user_id TEXT PRIMARY KEY, | |
| name TEXT, | |
| tier TEXT, | |
| balance REAL, | |
| status TEXT, | |
| email TEXT, | |
| phone TEXT, | |
| dob TEXT, | |
| address TEXT, | |
| account_opened TEXT, | |
| kyc_status TEXT, | |
| fraud_flags TEXT | |
| ) | |
| """) | |
| cur.execute(""" | |
| CREATE TABLE IF NOT EXISTS transactions ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| user_id TEXT, | |
| date TEXT, | |
| type TEXT, | |
| amount REAL, | |
| description TEXT, | |
| recipient TEXT, | |
| status TEXT, | |
| FOREIGN KEY (user_id) REFERENCES customers(user_id) | |
| ) | |
| """) | |
| # ββ Seed customers (upsert) ββ | |
| for c in CUSTOMERS: | |
| cur.execute( | |
| """INSERT INTO customers | |
| (user_id, name, tier, balance, status, email, phone, dob, | |
| address, account_opened, kyc_status, fraud_flags) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | |
| ON CONFLICT(user_id) DO UPDATE SET | |
| name=excluded.name, tier=excluded.tier, balance=excluded.balance, | |
| status=excluded.status, email=excluded.email, phone=excluded.phone, | |
| dob=excluded.dob, address=excluded.address, | |
| account_opened=excluded.account_opened, kyc_status=excluded.kyc_status, | |
| fraud_flags=excluded.fraud_flags | |
| """, | |
| ( | |
| c["user_id"], c["name"], c["tier"], c["balance"], | |
| c["status"], c["email"], c["phone"], c["dob"], | |
| c["address"], c["account_opened"], c["kyc_status"], | |
| json.dumps(c.get("fraud_flags", [])), | |
| ), | |
| ) | |
| # ββ Seed transactions (only if table is empty) ββ | |
| count = cur.execute("SELECT COUNT(*) FROM transactions").fetchone()[0] | |
| if count == 0: | |
| for t in TRANSACTIONS: | |
| cur.execute( | |
| """INSERT INTO transactions | |
| (user_id, date, type, amount, description, recipient, status) | |
| VALUES (?, ?, ?, ?, ?, ?, ?)""", | |
| ( | |
| t["user_id"], t["date"], t["type"], t["amount"], | |
| t.get("description"), t.get("recipient"), t["status"], | |
| ), | |
| ) | |
| conn.commit() | |
| def get_customer(conn: sqlite3.Connection, user_id: str) -> dict | None: | |
| """Retrieve a single customer by user_id.""" | |
| row = conn.execute( | |
| "SELECT * FROM customers WHERE user_id = ?", (user_id,) | |
| ).fetchone() | |
| if row is None: | |
| return None | |
| d = dict(row) | |
| d["fraud_flags"] = json.loads(d.get("fraud_flags") or "[]") | |
| return d | |
| def get_transactions(conn: sqlite3.Connection, user_id: str) -> list[dict]: | |
| """Retrieve all transactions for a user_id, newest first.""" | |
| rows = conn.execute( | |
| "SELECT * FROM transactions WHERE user_id = ? ORDER BY date DESC", | |
| (user_id,), | |
| ).fetchall() | |
| return [dict(r) for r in rows] | |
| def authenticate_user(conn: sqlite3.Connection, user_id: str, name: str) -> dict | None: | |
| """Authenticate a user by matching user_id and name (case-insensitive).""" | |
| row = conn.execute( | |
| "SELECT * FROM customers WHERE user_id = ? AND LOWER(name) = LOWER(?)", | |
| (user_id, name), | |
| ).fetchone() | |
| if row is None: | |
| return None | |
| d = dict(row) | |
| d["fraud_flags"] = json.loads(d.get("fraud_flags") or "[]") | |
| return d | |
| def authenticate_by_name(conn: sqlite3.Connection, name: str) -> dict | None: | |
| """Authenticate a user by name only (case-insensitive). | |
| The user_id and tier are looked up from the database and returned, so the | |
| login form only needs to ask for the customer's name. No schema change β | |
| the underlying records are unchanged. | |
| """ | |
| row = conn.execute( | |
| "SELECT * FROM customers WHERE LOWER(name) = LOWER(?)", | |
| (name.strip(),), | |
| ).fetchone() | |
| if row is None: | |
| return None | |
| d = dict(row) | |
| d["fraud_flags"] = json.loads(d.get("fraud_flags") or "[]") | |
| return d | |
| def format_account_details(customer: dict, transactions: list[dict]) -> str: | |
| """Format customer account details and transactions into a readable string.""" | |
| if not customer: | |
| return "Account not found." | |
| lines = [ | |
| f"Account Details for {customer['name']}", | |
| f" User ID: {customer['user_id']}", | |
| f" Tier: {customer['tier']}", | |
| f" Balance: ${customer['balance']:,.2f}", | |
| f" Status: {customer['status']}", | |
| f" Email: {customer['email']}", | |
| f" Phone: {customer['phone']}", | |
| f" Account Opened: {customer['account_opened']}", | |
| f" KYC Status: {customer['kyc_status']}", | |
| ] | |
| flags = customer.get("fraud_flags", []) | |
| if flags: | |
| lines.append(f" Fraud Flags: {', '.join(flags)}") | |
| if transactions: | |
| lines.append("\nRecent Transactions:") | |
| for txn in transactions: | |
| amount_str = f"${txn['amount']:,.2f}" if txn["amount"] else "β" | |
| desc = txn.get("description") or "" | |
| recipient = txn.get("recipient") | |
| detail = f"{recipient} ({desc})" if recipient else desc | |
| lines.append( | |
| f" [{txn['date']}] {txn['type']} | {amount_str} | {detail} | {txn['status']}" | |
| ) | |
| return "\n".join(lines) | |