Backend¶
The backend is a FastAPI application written in Python 3.13, structured as a uv workspace member (swen-backend).
Package Layout¶
services/backend/src/
├── swen/ ← main application package
│ ├── application/ ← use cases, command/query handlers
│ ├── domain/ ← entities, value objects, domain services, ports
│ ├── infrastructure/ ← SQLAlchemy repos, FinTS adapter, ML client, SMTP
│ └── presentation/
│ ├── api/ ← FastAPI app factory, routers, schemas
│ └── cli/ ← CLI entry points (db-init, seed-demo, secrets)
├── swen_identity/ ← user management, auth, JWT
├── swen_config/ ← pydantic-settings config loader
└── swen_demo/ ← demo data seeder
App Factory¶
The FastAPI application is created via create_app() in swen/presentation/api/app.py. It:
- Loads settings from
swen_config - Registers all routers (
/api/v1/...) - Attaches the lifespan context manager (DB connection pool init/teardown)
- Configures CORS, cookie settings, and exception handlers
Dependency Injection¶
SWEN uses FastAPI's native Depends() for dependency injection:
@router.get("/transactions/{id}")
async def get_transaction(
id: UUID,
ctx: UserContext = Depends(get_current_user),
repo: TransactionRepository = Depends(get_transaction_repo),
) -> TransactionDTO:
...
Repository implementations are injected via Depends — this makes them easy to swap in tests (pass a mock repo instead of the SQLAlchemy one).
Settings¶
Settings are loaded by swen_config using pydantic-settings. The loader discovers the env file by checking:
APP_ENVenvironment variable (development→config/.env.dev, otherwiseconfig/.env)- The
config/directory mounted at/app/configin Docker
Key settings groups:
| Group | Examples |
|---|---|
| Database | POSTGRES_HOST, POSTGRES_PASSWORD, POSTGRES_DB |
| Security | ENCRYPTION_KEY, JWT_SECRET_KEY, API_COOKIE_SECURE |
| CORS | API_CORS_ORIGINS |
| SMTP | SMTP_HOST, SMTP_PORT, SMTP_ENABLED |
| Registration | REGISTRATION_MODE (open / admin_only) |
| ML | SWEN_ML_SERVICE_URL |
Authentication — JWT¶
SWEN uses a dual-token JWT strategy:
| Token | Lifetime | Storage | Purpose |
|---|---|---|---|
| Access token | 24 hours | In-memory (JavaScript) | API authentication |
| Refresh token | 30 days | HttpOnly cookie | Silent token refresh |
The access token is passed as a Bearer header. The refresh token is set as a Set-Cookie: refresh_token=...; HttpOnly; SameSite=Strict response on login and used by the /auth/refresh endpoint to issue a new access token without re-entering credentials.
API_COOKIE_SECURE=true must be set in production (HTTPS only).
Encryption at Rest¶
FinTS credentials (username + PIN) are encrypted with Fernet (AES-128-CBC + HMAC-SHA256) before being stored in the database. The ENCRYPTION_KEY in config/.env is the Fernet key. Never commit this key.
Key Application Services¶
| Service | Responsibility |
|---|---|
TransactionImportService |
Orchestrates a full bank sync: FinTS fetch → dedup → ML classify → Draft creation |
MLBatchClassificationService |
Re-classifies all unclassified Draft transactions in bulk |
AccountMappingService |
Creates and validates the link between a BankAccount and its bookkeeping Account |
CLI Entry Points¶
Defined in pyproject.toml [project.scripts]:
| Command | What it does |
|---|---|
swen setup |
Interactive guided setup wizard (generates secrets, writes .env, runs db-init) |
swen secrets generate |
Prints three freshly-generated secrets (Fernet key, JWT key, DB password) |
db-init |
Creates all database tables (idempotent) |
db-drop |
Drops all database tables (destructive — no reset, just drop) |
db-reset |
Drops and recreates all tables (destructive) |
seed-demo |
Creates a demo user + sample accounts + ~200 transactions |