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

5.4 KiB

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)

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

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)

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
  • ENVIRONMENTdevelopment 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)