|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Full-stack web application template: **FastAPI** backend + **React/TypeScript** frontend. Monorepo with `backend/` and `frontend/` as separate workspaces managed by **uv** (Python) and **bun** (JS). |
| 8 | + |
| 9 | +## Common Commands |
| 10 | + |
| 11 | +### Running the Stack (Docker Compose — recommended) |
| 12 | +```bash |
| 13 | +docker compose watch # Start all services with hot-reload |
| 14 | +docker compose up -d # Start in background |
| 15 | +docker compose logs backend -f # Tail backend logs |
| 16 | +``` |
| 17 | + |
| 18 | +### Backend |
| 19 | +```bash |
| 20 | +cd backend |
| 21 | +uv sync # Install dependencies |
| 22 | +fastapi dev app/main.py # Dev server on :8000 (hot-reload) |
| 23 | + |
| 24 | +# Lint & type-check |
| 25 | +bash scripts/lint.sh # Runs: mypy app && ruff check app && ruff format app --check |
| 26 | + |
| 27 | +# Tests (requires running PostgreSQL — use docker compose for DB) |
| 28 | +bash scripts/test.sh # coverage run + pytest + report |
| 29 | +uv run pytest tests/ # Run all tests |
| 30 | +uv run pytest tests/api/routes/test_items.py # Single test file |
| 31 | +uv run pytest tests/api/routes/test_items.py::test_create_item # Single test |
| 32 | + |
| 33 | +# Alembic migrations |
| 34 | +uv run alembic revision --autogenerate -m "description" |
| 35 | +uv run alembic upgrade head |
| 36 | +``` |
| 37 | + |
| 38 | +### Frontend |
| 39 | +```bash |
| 40 | +cd frontend |
| 41 | +bun install # Install dependencies |
| 42 | +bun run dev # Dev server on :5173 |
| 43 | +bun run build # TypeScript check + Vite build |
| 44 | +bun run lint # Biome check --write --unsafe |
| 45 | +bun run test # Playwright E2E tests |
| 46 | +``` |
| 47 | + |
| 48 | +### Regenerate Frontend API Client |
| 49 | +```bash |
| 50 | +# Requires backend running (exports OpenAPI JSON, generates TypeScript client) |
| 51 | +bash scripts/generate-client.sh |
| 52 | +``` |
| 53 | + |
| 54 | +### Pre-commit Hooks |
| 55 | +```bash |
| 56 | +cd backend && uv run prek install -f # Install hooks (uses prek, not pre-commit) |
| 57 | +uv run prek run --all-files # Run all hooks manually |
| 58 | +``` |
| 59 | + |
| 60 | +## Architecture |
| 61 | + |
| 62 | +### Backend (`backend/`) |
| 63 | +- **FastAPI** with **SQLModel** (SQLAlchemy + Pydantic hybrid) ORM |
| 64 | +- **PostgreSQL 18** via `psycopg` (psycopg3) driver |
| 65 | +- **Alembic** for DB migrations (auto-runs on container start via `prestart` service) |
| 66 | +- **JWT auth** (HS256 via `pyjwt`) with **Argon2** password hashing (bcrypt legacy fallback via `pwdlib`) |
| 67 | +- API routes: `backend/app/api/routes/` — `login`, `users`, `items`, `utils`, `private` (local-only) |
| 68 | +- Settings via `pydantic-settings` reading from root `.env` file, split into sub-configs (`DatabaseSettings`, `EmailSettings`) |
| 69 | +- Email via `emails` library + Jinja2 templates (MJML source → compiled HTML in `email-templates/`) |
| 70 | +- Sentry integration (disabled in `local` environment) |
| 71 | +- API docs hidden in production (only available in `local`/`staging`) |
| 72 | + |
| 73 | +#### Domain-based structure |
| 74 | +Code is organized by domain, each with its own models, service, dependencies, and constants: |
| 75 | + |
| 76 | +``` |
| 77 | +backend/app/ |
| 78 | +├── main.py # App entry point, CORS, Sentry, global DomainError handler |
| 79 | +├── api/ |
| 80 | +│ ├── main.py # Router mounting |
| 81 | +│ ├── deps.py # Common dependencies (SessionDep, CurrentUser, etc.) |
| 82 | +│ └── routes/ # Thin route handlers (HTTP protocol only) |
| 83 | +│ ├── login.py |
| 84 | +│ ├── users.py |
| 85 | +│ ├── items.py |
| 86 | +│ ├── utils.py |
| 87 | +│ └── private.py # Local-only, uses service layer |
| 88 | +├── core/ |
| 89 | +│ ├── config.py # Settings + DatabaseSettings + EmailSettings |
| 90 | +│ ├── security.py # JWT token creation, password hashing |
| 91 | +│ └── db.py # Engine, naming convention, init_db |
| 92 | +├── common/ |
| 93 | +│ ├── models.py # Shared models (Message, Token, TokenPayload, NewPassword), get_datetime_utc |
| 94 | +│ └── exceptions.py # DomainError base class, InvalidResetTokenError, CredentialsValidationError |
| 95 | +├── users/ |
| 96 | +│ ├── models.py # User, UserCreate, UserUpdate, UserPublic, etc. |
| 97 | +│ ├── service.py # Business logic (create, update, authenticate, etc.) |
| 98 | +│ ├── dependencies.py # valid_user_id, ValidUser |
| 99 | +│ ├── exceptions.py # UserNotFoundError, UserAlreadyExistsError, etc. |
| 100 | +│ └── constants.py # Success message constants (PASSWORD_UPDATED, USER_DELETED) |
| 101 | +├── items/ |
| 102 | +│ ├── models.py # Item, ItemCreate, ItemUpdate, ItemPublic, etc. |
| 103 | +│ ├── service.py # Business logic (create, update, delete, get_items) |
| 104 | +│ ├── dependencies.py # valid_item_id, ValidItem, valid_owned_item (chained) |
| 105 | +│ ├── exceptions.py # ItemNotFoundError, ItemPermissionError |
| 106 | +│ └── constants.py # Success message constants (ITEM_DELETED) |
| 107 | +├── utils.py # Email utilities (send, generate templates) |
| 108 | +├── initial_data.py # Seed superuser |
| 109 | +└── backend_pre_start.py # DB readiness check |
| 110 | +``` |
| 111 | + |
| 112 | +Key conventions: |
| 113 | +- **Routes are thin** — only HTTP protocol handling, business logic in `service.py` |
| 114 | +- **Dependencies chain** — e.g. `valid_owned_item` depends on `valid_item_id` (FastAPI caches per request) |
| 115 | +- **Domain exceptions** — each domain defines exception classes inheriting `DomainError(status_code, detail)`; a single global handler in `main.py` converts any `DomainError` to the matching HTTP response. No `HTTPException` in the codebase. |
| 116 | +- **Constants are for success messages only** — error messages live in exception classes as the single source of truth |
| 117 | +- **Settings sub-configs** — access via `settings.db.POSTGRES_*`, `settings.email.SMTP_*` |
| 118 | + |
| 119 | +Key files: |
| 120 | +- `app/core/config.py` — `Settings`, `DatabaseSettings`, `EmailSettings` |
| 121 | +- `app/core/db.py` — Engine + DB naming convention (`pk_`, `fk_`, `ix_`, `uq_`, `ck_`) |
| 122 | +- `app/api/deps.py` — Dependency injection (`SessionDep`, `CurrentUser`) |
| 123 | +- `app/common/exceptions.py` — `DomainError` base class (all domain exceptions inherit from it, carrying `status_code` + `detail`) |
| 124 | + |
| 125 | +### Frontend (`frontend/`) |
| 126 | +- **React 19** + **TypeScript 5.9** + **Vite 7** (SWC plugin) |
| 127 | +- **TanStack Router** (file-based routing in `src/routes/`, auto-generates `routeTree.gen.ts`) |
| 128 | +- **TanStack Query** (React Query v5) for server state |
| 129 | +- **Tailwind CSS v4** + **shadcn/ui** components (in `src/components/ui/`) |
| 130 | +- **Biome v2** for linting/formatting |
| 131 | +- API client auto-generated from OpenAPI spec via `@hey-api/openapi-ts` → `src/client/` |
| 132 | +- Forms: `react-hook-form` + `zod v4` validation |
| 133 | +- Auth guard in `_layout.tsx` redirects unauthenticated users to `/login` |
| 134 | + |
| 135 | +### Auto-generated Files (do not edit manually) |
| 136 | +- `frontend/src/client/` — Generated API client (regenerate via `scripts/generate-client.sh`) |
| 137 | +- `frontend/src/routeTree.gen.ts` — Generated by TanStack Router plugin |
| 138 | +- `backend/app/email-templates/build/` — Compiled from MJML sources |
| 139 | + |
| 140 | +### Docker Services |
| 141 | +- `db` — PostgreSQL 18 |
| 142 | +- `prestart` — Runs migrations + seeds superuser |
| 143 | +- `backend` — FastAPI (port 8000) |
| 144 | +- `frontend` — Nginx serving built React app (port 80→5173 in dev) |
| 145 | +- `mailcatcher` — Local email testing (SMTP 1025, Web UI 1080) |
| 146 | +- Traefik reverse proxy routes: `api.<DOMAIN>` → backend, `dashboard.<DOMAIN>` → frontend |
| 147 | + |
| 148 | +## Code Style & Conventions |
| 149 | + |
| 150 | +### Backend |
| 151 | +- Python ≥3.10, mypy strict mode |
| 152 | +- Ruff rules: pycodestyle, pyflakes, isort, flake8-bugbear, comprehensions, pyupgrade, no print statements |
| 153 | +- Alembic directory excluded from ruff and mypy |
| 154 | +- Alembic migration naming: `YYYY-MM-DD_slug_revid.py` (via `file_template` in `alembic.ini`) |
| 155 | +- DB index naming convention: `pk_<table>`, `fk_<table>_<col>_<ref_table>`, `ix_<col>`, `uq_<table>_<col>`, `ck_<table>_<name>` |
| 156 | +- Tests require a running PostgreSQL instance; CI enforces ≥90% coverage |
| 157 | + |
| 158 | +### Frontend |
| 159 | +- Biome excludes: `dist/`, `src/routeTree.gen.ts`, `src/client/`, `src/components/ui/` |
| 160 | +- Path alias: `@` maps to `src/` |
| 161 | +- Dark mode default via `next-themes` |
| 162 | + |
| 163 | +## Environment |
| 164 | +- **`.env.example`** — Committed template with safe placeholder values (`changethis`). Developers copy to `.env` locally. |
| 165 | +- **`.env`** — Local config (gitignored). Shared by backend (pydantic-settings) and Docker Compose. |
| 166 | +- **`.env.local`** — Optional personal overrides (gitignored). Values here override `.env`, just like frontend's `.env.local` pattern. |
| 167 | +- **Priority chain**: environment variables > `.env.local` > `.env` > code defaults |
| 168 | +- Settings via `pydantic-settings` with `env_file=("../.env", "../.env.local")` — later files override earlier ones |
| 169 | +- Sub-configs: `settings.db.POSTGRES_*`, `settings.email.SMTP_*` |
| 170 | +- Key variables: `DOMAIN`, `SECRET_KEY`, `FIRST_SUPERUSER`/`FIRST_SUPERUSER_PASSWORD`, `POSTGRES_*`, `SMTP_*` |
| 171 | +- Production/CI: inject via environment variables (highest priority), no env files needed |
0 commit comments