Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 0 additions & 45 deletions .env

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Deploy to Staging
on:
push:
branches:
- master
- main

jobs:
deploy:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/latest-changes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Latest Changes
on:
pull_request_target:
branches:
- master
- main
types:
- closed
workflow_dispatch:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Playwright Tests
on:
push:
branches:
- master
- main
pull_request:
types:
- opened
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Test Backend
on:
push:
branches:
- master
- main
pull_request:
types:
- opened
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Test Docker Compose
on:
push:
branches:
- master
- main
pull_request:
types:
- opened
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.env
.env.*
!.env.example
.vscode/*
!.vscode/extensions.json
node_modules/
Expand Down
117 changes: 117 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# CLAUDE.md

## Project Overview

Full-stack web app template: FastAPI backend + React frontend + PostgreSQL. JWT auth, user management, CRUD, auto-generated API client. Docker Compose-based monorepo with Traefik reverse proxy.

## Technology Stack

| Category | Technology |
|----------|-----------|
| Backend | Python >=3.10, FastAPI >=0.114.2 |
| ORM | SQLModel >=0.0.21 (SQLAlchemy) |
| Database | PostgreSQL 18, Alembic migrations |
| Frontend | TypeScript 5.9, React 19.1, Vite 7.3 (SWC) |
| Routing | TanStack Router 1.157+ (file-based) |
| Server State | TanStack Query 5.90+ |
| Styling | Tailwind CSS 4.2, shadcn/ui (new-york) |
| Auth | JWT (HS256) via PyJWT, Argon2+Bcrypt |

**Key Libraries:** Zod 4.x, React Hook Form 7.x, Axios 1.13, lucide-react, sonner

## Architecture Overview

**Pattern:** Layered monorepo — `backend/` (Python) + `frontend/` (TypeScript)

```
backend/app/
api/routes/ # FastAPI endpoint handlers
core/ # config.py, db.py, security.py
alembic/versions/ # Database migrations
models.py # SQLModel tables + Pydantic schemas
crud.py # Data access functions
frontend/src/
routes/ # TanStack file-based routing (pages)
components/ # UI (Admin/, Items/, Common/, UserSettings/, Sidebar/, ui/)
client/ # Auto-generated OpenAPI client — DON'T EDIT
hooks/ # Custom React hooks
```

**Important Patterns:**
- Backend models: `ModelBase → ModelCreate → ModelUpdate → Model(table=True) → ModelPublic`
- `src/client/`, `src/routeTree.gen.ts`, `src/components/ui/` are auto-generated — don't edit

**Key Files:** `backend/app/main.py` (FastAPI entry), `backend/app/core/config.py` (settings from `.env`), `frontend/src/main.tsx` (React app + QueryClient + Router)

## Development Commands

```bash
# Setup
cd backend && uv sync # Backend deps
bun install # Frontend deps
cd backend && uv run prek install -f # Pre-commit hooks
docker compose watch # Start full stack

# Development
docker compose watch # Full stack (recommended)
bun run dev # Frontend only (:5173)
cd backend && fastapi dev app/main.py # Backend only (:8000)

# Testing
bash ./scripts/test.sh # All backend tests (Pytest)
bunx playwright test # Frontend E2E
bunx playwright test tests/login.spec.ts # Single E2E test

# Quality
bun run lint # Frontend (Biome)
uv run ruff check --fix # Backend lint
uv run ruff format # Backend format
uv run mypy backend/app # Backend type check

# Utilities
bash ./scripts/generate-client.sh # Regenerate OpenAPI client
```

## Code Conventions

- **Backend:** snake_case files/functions, PascalCase classes, absolute imports `from app.*`, Ruff + strict mypy
- **Frontend:** PascalCase components, camelCase utils, `@/` alias → `./src/*`, Biome (double quotes, no semicolons)

## Testing

**Backend:** Pytest in `backend/tests/` (api/routes/, crud/, scripts/) — `uv run pytest backend/tests/path/to/test.py`
**Frontend:** Playwright E2E in `frontend/tests/` — `bunx playwright test tests/file.spec.ts`

See `@docs/testing/strategy.md` for coverage requirements and mocking patterns.

## Database & Migrations

**ORM:** SQLModel | **Models:** `backend/app/models.py`

```bash
alembic revision --autogenerate -m "desc" # Create migration
alembic upgrade head # Apply pending
alembic downgrade -1 # Rollback last
alembic history --verbose # View history
```

Always review autogenerated migrations before applying. See `@docs/data/models.md`.

## Known Issues

- `SECRET_KEY`, `POSTGRES_PASSWORD`, `FIRST_SUPERUSER_PASSWORD` default to `changethis` — change for staging/production
- Backend syntax errors crash dev container — restart with `docker compose watch`
- Pre-commit hook auto-regenerates frontend SDK on backend changes
- Alembic autogenerated migrations need manual review

## Documentation

Key references in `docs/`:
- `@docs/getting-started/setup.md` - Setup and environment
- `@docs/getting-started/development.md` - Daily development workflow
- `@docs/architecture/overview.md` - System architecture
- `@docs/api/overview.md` - API documentation
- `@docs/data/models.md` - Data models and schemas
- `@docs/testing/strategy.md` - Testing approach
- `@docs/deployment/environments.md` - Deployment guides
- `@docs/deployment/ci-pipeline.md` - CI/CD pipeline documentation
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ git remote add upstream git@github.com:fastapi/full-stack-fastapi-template.git
- Push the code to your new repository:

```bash
git push -u origin master
git push -u origin main
```

### Update From the Original Template
Expand All @@ -107,7 +107,7 @@ upstream git@github.com:fastapi/full-stack-fastapi-template.git (push)
- Pull the latest changes without merging:

```bash
git pull --no-commit upstream master
git pull --no-commit upstream main
```

This will download the latest changes from this template without committing them, that way you can check everything is right before committing.
Expand Down Expand Up @@ -184,7 +184,7 @@ If you have `pipx` and you didn't install `copier`, you can run it directly:
pipx run copier copy https://github.com/fastapi/full-stack-fastapi-template my-awesome-project --trust
```

**Note** the `--trust` option is necessary to be able to execute a [post-creation script](https://github.com/fastapi/full-stack-fastapi-template/blob/master/.copier/update_dotenv.py) that updates your `.env` files.
**Note** the `--trust` option is necessary to be able to execute a [post-creation script](https://github.com/fastapi/full-stack-fastapi-template/blob/main/.copier/update_dotenv.py) that updates your `.env` files.

### Input Variables

Expand Down
73 changes: 29 additions & 44 deletions backend/app/api/deps.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,42 @@
from collections.abc import Generator
from typing import Annotated
"""Typed FastAPI dependency declarations.

import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jwt.exceptions import InvalidTokenError
from pydantic import ValidationError
from sqlmodel import Session
All cross-cutting concerns (database, auth, HTTP client, request context) are
injected via ``Annotated[T, Depends(...)]`` types. Route handlers declare what
they need through parameter type annotations and FastAPI resolves the
dependency chain automatically.

from app.core import security
from app.core.config import settings
from app.core.db import engine
from app.models import TokenPayload, User
Every dependency listed here is overridable in tests via
``app.dependency_overrides[fn] = mock_fn``.
"""

reusable_oauth2 = OAuth2PasswordBearer(
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
)
from typing import Annotated

from fastapi import Depends, Request
from supabase import Client as SupabaseClient

def get_db() -> Generator[Session, None, None]:
with Session(engine) as session:
yield session
from app.core.auth import get_current_principal
from app.core.http_client import HttpClient, get_http_client
from app.core.supabase import get_supabase
from app.models.auth import Principal


SessionDep = Annotated[Session, Depends(get_db)]
TokenDep = Annotated[str, Depends(reusable_oauth2)]
def get_request_id(request: Request) -> str:
"""Return the current request ID from request state.

The request_id is set by RequestPipelineMiddleware on every request.
Falls back to an empty string if middleware has not run (e.g. in tests).
"""
return getattr(request.state, "request_id", "")

def get_current_user(session: SessionDep, token: TokenDep) -> User:
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
)
token_data = TokenPayload(**payload)
except (InvalidTokenError, ValidationError):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Could not validate credentials",
)
user = session.get(User, token_data.sub)
if not user:
raise HTTPException(status_code=404, detail="User not found")
if not user.is_active:
raise HTTPException(status_code=400, detail="Inactive user")
return user

SupabaseDep = Annotated[SupabaseClient, Depends(get_supabase)]
"""Supabase client instance, initialised at app startup."""

CurrentUser = Annotated[User, Depends(get_current_user)]
PrincipalDep = Annotated[Principal, Depends(get_current_principal)]
"""Authenticated user principal extracted from Clerk JWT."""

HttpClientDep = Annotated[HttpClient, Depends(get_http_client)]
"""Shared async HTTP client with retry, circuit breaker, header propagation."""

def get_current_active_superuser(current_user: CurrentUser) -> User:
if not current_user.is_superuser:
raise HTTPException(
status_code=403, detail="The user doesn't have enough privileges"
)
return current_user
RequestIdDep = Annotated[str, Depends(get_request_id)]
"""Current request UUID from the request pipeline middleware."""
Loading
Loading