Documentation

Everything needed to install, understand, operate and extend the Danube Bank banking system.

Demo software. No real money, card network connectivity or KYC provider is involved. Card numbers are generated locally and accounts are seeded with fictitious balances.

Overview

Danube Bank is a two-application system sharing one MySQL database:

Both import the same ORM models and the same banking service layer, so there is exactly one implementation of money movement. The operator app uses the same ledger functions rather than editing balances directly.

LayerTechnology
Web frameworkFastAPI + Starlette
ORMSQLAlchemy 2.0 (typed mappings)
DatabaseMySQL 8.4 in Docker (utf8mb4)
TemplatesJinja2, server-rendered
SessionsSigned cookies (`SessionMiddleware`)
Hashing / cryptoscrypt (stdlib), Fernet (cryptography)
Testspytest, 14 unit + ledger integration tests

Quick start

Requirements: Docker Desktop and Python 3.11+.

docker compose up -d
python -m venv .venv
.venv\Scripts\activate          # Windows
pip install -r requirements.txt
cp .env.example .env
python -m scripts.seed
uvicorn app.main:app --reload
uvicorn admin_app.main:app --port 8001

Open http://127.0.0.1:8000 for customers and http://127.0.0.1:8001 for operators.

EmailPasswordAccess
alex@danube.demoPassword123!Customer with accounts, card and loan
maria@danube.demoPassword123!Customer, KYC pending
andrei@danube.demoPassword123!Customer, useful transfer target
admin@danube.demoPassword123!Operator / admin

Reset the database at any time with python -m scripts.seed --reset.

Configuration

Environment (.env)

VariablePurpose
SECRET_KEYSigns session cookies and derives the card-encryption key
DATABASE_URLSQLAlchemy URL, default mysql+pymysql://banker:bankerpass@127.0.0.1:3306/banking
DEMO_PASSWORDPassword assigned to seeded demo users
ALLOW_ADMIN_SQL_WRITE1 enables INSERT/UPDATE/DELETE in the operator SQL console

Runtime settings (MySQL)

Institution name, support email, currency, transfer and cash limits, overdraft, savings interest, card limits, loan rates and limits, maintenance mode and demo-login visibility all live in the settings table. Operators edit them in Settings; both apps refresh their cache every 10 seconds. Changes are audited individually.

Architecture

app/
  main.py            customer app entrypoint and maintenance gate
  models.py          customers, accounts, cards, ledger, loans, settings, audit
  security.py        scrypt hashing, Luhn, Fernet-encrypted PANs
  deps.py            auth, CSRF, template rendering
  routers/           auth, accounts, transfers, cards, loans, admin (customer-side)
  services/banking.py   the only code that moves money
  services/settings.py  cached DB settings
  services/queries.py   read models for dashboards and statements
  templates/ static/
admin_app/
  main.py            operator app entrypoint (port 8001)
  auth.py            operator login (role=admin)
  views.py           dashboard, customers, accounts
  banking_ops.py     transactions, reversals, cards, loans
  system.py          settings, DB explorer, SQL console, backups, audit
  templates/ static/
scripts/seed.py      schema, settings defaults, demo data
docs/                this GitHub Pages site
tests/               unit and ledger integration tests

Ledger design

Money is stored in integer cents. A transaction is a header row; each account touched gets a ledger_entries row with the signed amount and the resulting balance.

Transaction  TXN-8F2A…   transfer   250.00 USD
 ├─ LedgerEntry  account A   −25000   balance_after 175000
 └─ LedgerEntry  account B   +25000   balance_after  25000

Security model

This is a demo. There is no TLS termination, MFA, distributed rate limiting, email verification or real KYC integration. Do not use it to handle real money.

Accounts, transfers and statements

Customers can hold up to the configured number of open accounts. Checking accounts carry an overdraft facility; savings accounts accrue interest through the daily batch job.

Transfers are local (same bank, same currency). The recipient account is resolved by account number, validated, and both accounts post in one database transaction. Receipts are addressable by reference, CSV exports and printable statements are generated from ledger entries.

Standing orders support daily, weekly and monthly frequencies. When the batch job runs, due plans execute; if funds are insufficient the plan is paused and the customer is notified.

Cards

Cards use the demo BIN 400000 and a Luhn check digit. Issuing generates a 4-digit PIN unless one is supplied. Daily limits are enforced per card and reset automatically on the next day. Operators can freeze, cancel, change limits and reset PINs from either app.

Loans

Loan pricing is a base rate with discounts for verified KYC and larger amounts, all configurable in Settings. Approval creates an annuity amortisation schedule, disburses principal to the chosen account and marks instalments paid as they are repaid. Early repayment closes the loan immediately.

Operator app (Danube Ops)

Sign in at http://127.0.0.1:8001 with an account whose role is admin.

Database tools

The operator app includes a real database explorer:

HTTP endpoints

The customer app is server-rendered, so most routes return HTML. This table lists the route groups.

Customer app (:8000)Purpose
GET /healthJSON health check
GET/POST /login, /registerAuthentication
GET /dashboardBalances, chart, recent activity
GET /accounts/{id}, /statement, /export.csvAccount detail and exports
POST /accounts/{id}/deposit|withdrawCash movements
GET/POST /transfer, GET /receipt/{ref}Transfers and receipts
GET/POST /beneficiaries, /scheduledPayees and standing orders
GET/POST /cards, POST /cards/{id}/…Card lifecycle
GET/POST /loans, POST /loans/{id}/…Loans and repayment
GET /admin/…Basic admin pages for role=admin
GET /api/docsOpenAPI documentation (FastAPI)
Operator app (:8001)Purpose
GET /healthJSON health check
GET /customers, /customers/{id}Customer management
GET /accounts, /accounts/{id}Account management and adjustments
GET /transactions, /transactions/{ref}Ledger inspection and reversal
GET /cards, /loansCard and loan operations
GET/POST /settingsRuntime configuration
GET /database, /database/{table}, /database/backup.sqlDatabase explorer and backup
GET/POST /sqlAudited SQL console
GET /auditAudit trail

Testing

pytest

Unit tests cover Luhn generation, scrypt hashing, annuity maths and money formatting. Ledger tests run against MySQL, verify balanced entries, overdraft limits, ownership checks and idempotency, then roll back so they leave no data behind. If MySQL is not reachable they skip.

Deployment

For a shared environment:

This documentation site is published with GitHub Pages from docs/ and served at bankingsystem.danubelabs.net.

Troubleshooting

SymptomFix
Can't connect to MySQLdocker compose ps — wait for the healthcheck, then rerun.
Login says password is wrongReseed: python -m scripts.seed --reset.
404 on /accounts/newEnsure dynamic /accounts/{id} routes are declared after static ones.
Settings changes not visibleWait 10 seconds for the cache refresh, then reload.
Card reveal disabledEnable it under Settings → Security.

Questions or ideas?

Open an issue on GitHub — the project is meant to be read and extended.