digitalBanking / banking /__init__.py
nvtitan's picture
Upload 39 files
8e2e76b verified
from __future__ import annotations
import os
import uuid
from fastapi import FastAPI, Depends, Header, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from .config import get_database
from .storage import BankingStorage, ChatHistoryStore
from .vectorstore import VectorStore
from chatbot.responder import GeminiChatResponder
from chatbot.service import ChatbotService
def create_app() -> FastAPI:
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
templates = os.path.join(base_dir, "templates")
static_dir = os.path.join(base_dir, "static")
app = FastAPI(title="Aurora Banking Chatbot")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.mount("/static", StaticFiles(directory=static_dir), name="static")
template_engine = Jinja2Templates(directory=templates)
database = get_database()
storage = BankingStorage(database)
history = ChatHistoryStore(storage.chat_sessions)
vectors = VectorStore()
vectors.bootstrap(storage.all_users())
responder = GeminiChatResponder()
service = ChatbotService(storage, history, vectors, responder)
sessions: dict[str, str] = {}
def get_session(authorization: str = Header(None)) -> tuple[str, str]:
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Unauthorized")
token = authorization.split(" ")[1]
username = sessions.get(token)
if not username:
raise HTTPException(status_code=401, detail="Unauthorized")
return username, token
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return template_engine.TemplateResponse("index.html", {"request": request})
@app.post("/api/login")
async def login(payload: dict):
username = payload.get("username", "").lower()
password = payload.get("password", "")
user_record = storage.authenticate(username, password)
if not user_record:
raise HTTPException(status_code=401, detail="Invalid credentials")
token = uuid.uuid4().hex
sessions[token] = username
history.reset(token, username)
snapshot = service.load_snapshot(username)
return {"token": token, "user": {"username": username}, "snapshot": snapshot}
@app.post("/api/chat")
async def chat(payload: dict, session=Depends(get_session)):
username, token = session
message = (payload.get("message") or "").strip()
if not message:
raise HTTPException(status_code=400, detail="Message cannot be empty")
reply, action_details, backend_data, updated_history, snapshot_after = service.run_agent(
username, token, message
)
return {
"reply": reply,
"history": updated_history,
"action": action_details,
"backend": backend_data,
"snapshot": snapshot_after,
}
@app.get("/api/accounts")
async def list_accounts(session=Depends(get_session)):
username, _ = session
return {"accounts": storage.list_accounts(username)}
@app.get("/api/transactions")
async def list_transactions(account_id: str | None = None, session=Depends(get_session)):
username, _ = session
return {"transactions": storage.list_transactions(username, account_id)}
@app.get("/api/beneficiaries")
async def list_beneficiaries(session=Depends(get_session)):
username, _ = session
return {"beneficiaries": storage.list_beneficiaries(username)}
@app.post("/api/beneficiaries")
async def add_beneficiary(payload: dict, session=Depends(get_session)):
username, _ = session
required = ["name", "account_number", "bank"]
if not all(payload.get(field) for field in required):
raise HTTPException(status_code=400, detail="Missing beneficiary details")
result = storage.add_beneficiary(username, payload)
vectors.rebuild_user(username, storage.get_user(username) or {})
return {"beneficiary": result}
@app.post("/api/transfers")
async def transfer_funds(payload: dict, session=Depends(get_session)):
username, _ = session
try:
amount = float(payload.get("amount", 0))
except (TypeError, ValueError):
raise HTTPException(status_code=400, detail="Amount must be numeric")
if amount <= 0:
raise HTTPException(status_code=400, detail="Transfer amount must be positive")
for key in ["from_account_id", "beneficiary_id"]:
if not payload.get(key):
raise HTTPException(status_code=400, detail=f"Missing {key}")
try:
transaction, account = storage.transfer_funds(
username,
payload["from_account_id"],
payload["beneficiary_id"],
amount,
payload.get("description", "Transfer to beneficiary"),
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
if not transaction:
raise HTTPException(status_code=400, detail="Unable to complete transfer")
vectors.rebuild_user(username, storage.get_user(username) or {})
return {"transaction": transaction, "account": account}
@app.post("/api/internal-transfers")
async def transfer_between_accounts(payload: dict, session=Depends(get_session)):
username, _ = session
try:
amount = float(payload.get("amount", 0))
except (TypeError, ValueError):
raise HTTPException(status_code=400, detail="Amount must be numeric")
if amount <= 0:
raise HTTPException(status_code=400, detail="Transfer amount must be positive")
for key in ["from_account_id", "to_account_id"]:
if not payload.get(key):
raise HTTPException(status_code=400, detail=f"Missing {key}")
try:
result = storage.transfer_between_accounts(
username,
payload["from_account_id"],
payload["to_account_id"],
amount,
payload.get("description", "Transfer between accounts"),
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
if not result:
raise HTTPException(status_code=400, detail="Unable to complete transfer")
vectors.rebuild_user(username, storage.get_user(username) or {})
return result
return app