Phase 3 — Investments: - Multi-currency support: holdings track purchase currency, FX rates convert to base for totals - Capital gains report using UK Section 104 pool method, grouped by tax year - Capital Gains tab added to Reports page Phase 5 — Polish & Hardening: - Mobile-responsive layout: bottom nav, sidebar hidden on mobile, logo in TopBar, compact header buttons, hover-only actions now always visible on touch - Backup system: encrypted GPG backups via backup.sh, nightly scheduler job, admin API (list/trigger/download/restore), Settings UI with drag-to-restore confirmation - Docker entrypoint with gosu privilege drop to fix bind-mount ownership on fresh deployments - OWASP fixes: refresh token now bound to its session (new refresh_token_hash column + migration), CSRF secure flag tied to environment, IP-level rate limiting on login, TOTPEnableRequest Pydantic schema replaces raw dict - AES-256-GCM key rotation script (rotate_keys.py) with dry-run mode and atomic DB transaction - CLAUDE.md added for AI-assisted development context - README updated: correct reverse proxy port, accurate backup/restore commands, key rotation instructions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
129 lines
5.4 KiB
Markdown
129 lines
5.4 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
**MyMidas** — a self-hosted personal finance tracker. Full-stack: FastAPI backend + React/TypeScript frontend, running in Docker Compose with PostgreSQL and Redis.
|
|
|
|
---
|
|
|
|
## Commands
|
|
|
|
### Backend (Python 3.12 + FastAPI)
|
|
```bash
|
|
# Dev server (from backend/)
|
|
python -m uvicorn app.main:app --reload --port 8080
|
|
|
|
# Apply migrations
|
|
python -m alembic upgrade head
|
|
|
|
# Create a new migration
|
|
python -m alembic revision --autogenerate -m "description"
|
|
|
|
# Run tests
|
|
pytest tests/
|
|
pytest tests/test_foo.py::test_bar # single test
|
|
```
|
|
|
|
### Frontend (React 18 + Vite)
|
|
```bash
|
|
cd frontend
|
|
npm run dev # Vite dev server on :5173, proxies /api → localhost:8080
|
|
npm run build # TypeScript check + production build
|
|
npm run lint # ESLint
|
|
```
|
|
|
|
### Docker (production)
|
|
```bash
|
|
docker compose up -d --build # Full rebuild and start
|
|
docker compose up -d --build backend # Rebuild backend only
|
|
docker compose logs -f backend # Follow backend logs
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Request flow
|
|
```
|
|
Browser → Frontend nginx (:4000) or Vite dev (:5173)
|
|
→ /api/* proxied to FastAPI (:8000 in Docker, :8080 in dev)
|
|
→ Middleware: SecurityHeaders → CSRF → CORSMiddleware
|
|
→ Rate limiter (Redis sliding window)
|
|
→ Route handler → get_current_user dependency
|
|
→ Service layer (business logic + encryption)
|
|
→ AsyncSession → PostgreSQL (RLS enforced)
|
|
```
|
|
|
|
### Auth architecture
|
|
- **JWT**: RS256, 15-min access tokens + 7-day HttpOnly refresh cookie
|
|
- `get_current_user` dependency validates the JWT, checks session exists and isn't revoked in DB, then calls `SET LOCAL app.current_user_id` to activate PostgreSQL RLS
|
|
- **TOTP**: optional TOTP on login; before TOTP is verified, a short-lived `challenge_token` is issued
|
|
- **CSRF**: double-submit cookie — backend sets `csrf_token` cookie on first GET; frontend reads it and sends `X-CSRF-Token` header on all mutating requests
|
|
|
|
### Backend layout
|
|
```
|
|
backend/app/
|
|
main.py — FastAPI app factory (middleware stack, router include)
|
|
config.py — Pydantic Settings from env vars
|
|
dependencies.py — get_db, get_redis, get_current_user
|
|
api/
|
|
router.py — Central router; investments/reports/budgets have no prefix (paths self-contained)
|
|
v1/ — One file per domain (auth, accounts, transactions, budgets, reports, investments, predictions)
|
|
db/models/ — SQLAlchemy 2.0 Mapped models
|
|
schemas/ — Pydantic request/response models (separate Create/Update/Response per domain)
|
|
services/ — Business logic; each service owns one domain
|
|
core/
|
|
security.py — Crypto primitives: Argon2id, RS256 JWT, AES-256-GCM, TOTP
|
|
middleware.py — CSRFMiddleware, SecurityHeadersMiddleware
|
|
workers/
|
|
scheduler.py — APScheduler in-process jobs
|
|
ml/ — Prophet/SARIMA forecasts, Monte Carlo simulations
|
|
```
|
|
|
|
### Service layer conventions
|
|
- All DB ops use `AsyncSession` + `await`
|
|
- PII fields (account name, transaction description/merchant/notes, TOTP secret) are AES-256-GCM encrypted; stored as `bytea` named with `_enc` suffix (e.g. `description_enc`). Use `encrypt_field` / `decrypt_field` from `core/security.py`
|
|
- Import deduplication via SHA-256 `import_hash` on transactions
|
|
- Every mutation writes to `AuditLog` (append-only; app role has no UPDATE/DELETE on that table)
|
|
- Soft deletes: `deleted_at` timestamp; all queries must filter `WHERE deleted_at IS NULL`
|
|
|
|
### Frontend layout
|
|
```
|
|
frontend/src/
|
|
api/ — Typed axios clients per domain; all use the shared client in api/client.ts
|
|
pages/ — Route-level components (one directory per domain)
|
|
components/ — Shared UI (layout, charts, modals)
|
|
store/ — Zustand: authStore (token/userId/displayName), uiStore (theme/sidebar/currency)
|
|
utils/ — Currency formatting, cn() class helper
|
|
```
|
|
|
|
### Frontend data fetching
|
|
TanStack Query (React Query v5) for all server state. `queryKey` conventions determine cache invalidation — always invalidate the correct key after mutations. The axios client in `api/client.ts` handles token injection, CSRF header, and 401 auto-refresh transparently.
|
|
|
|
### Background jobs (APScheduler, in-process)
|
|
- **Every 15 min**: sync asset prices (yfinance + CoinGecko)
|
|
- **Every 60 min**: sync FX rates
|
|
- **2 AM daily**: net worth snapshot
|
|
- **3 AM daily**: encrypted GPG backup
|
|
- **Sunday weekly**: retrain ML prediction models
|
|
|
|
### Database patterns
|
|
- UUID PKs everywhere
|
|
- PostgreSQL RLS policies on every table keyed to `app.current_user_id` (set per-request by `get_current_user`)
|
|
- `postgres/init/` contains init SQL; alembic manages schema evolution
|
|
- Migrations run automatically on container start (`alembic upgrade head` in Dockerfile CMD)
|
|
|
|
### Themes
|
|
7 CSS variable themes in `frontend/src/index.css`: obsidian (default dark), arctic (light), midnight, vault, terminal, synthwave, ledger. Applied as a class on `<html>`.
|
|
|
|
---
|
|
|
|
## Environment variables
|
|
See `.env.example` for the full list. Key ones:
|
|
- `ENCRYPTION_KEY` — 32-byte base64 for AES-256-GCM field encryption
|
|
- `DATABASE_URL` — asyncpg connection string
|
|
- `ENVIRONMENT` — `development` enables `/docs`, `/redoc`, `/openapi.json` and relaxes CORS
|
|
- `ALLOW_REGISTRATION` — gates the register endpoint (default `false` in prod)
|
|
- `BASE_CURRENCY` — default display currency (default `GBP`)
|