MyMidas/CLAUDE.md
megaproxy fe4e69b9ad Complete Phase 3, Phase 5 polish and hardening
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>
2026-04-22 14:59:11 +00:00

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`)