Skip to content

Latest commit

 

History

History
138 lines (99 loc) · 5.2 KB

File metadata and controls

138 lines (99 loc) · 5.2 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Overview

FastAPI-based backend providing JWT + API key authentication and user management. Designed to be extended with application-specific routes. Supports both SQLite (development) and PostgreSQL/TimescaleDB (production).

Setup & Running

# Install dependencies and generate .env
./setup.sh

# Start with Docker (auto-selects db profile from .env)
./start.sh

# Or run directly (runs migrations then starts server)
./entrypoint.sh

# Run server manually
poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

Database Migrations

# Apply migrations
poetry run alembic upgrade head

# Create a new migration (after modifying models)
poetry run alembic revision --autogenerate -m "description"

Important: alembic/env.py imports models via import app.models. When adding new model files, import them in env.py after the existing import. New models must inherit from the same Base class in app/models.py.

Linting & Formatting

poetry run black app/
poetry run isort app/
poetry run flake8 app/ --ignore=E501
poetry run mypy app/

Pre-commit hooks run black, isort, flake8, and mypy automatically on commit.

Python version: Keep target-version in [tool.black] (and the python = "^X.Y" constraint in [tool.poetry.dependencies]) in sync with the minimum Python version that OpenRVDAS ships on production. Don't bump these to match a dev machine's Python version.

Architecture

Request Flow

HTTP Request → FastAPI Route (app/api/*.py)
              → Auth dependency (app/auth.py decorators)
              → DB CRUD function (app/db/*.py)
              → SQLAlchemy AsyncSession (app/db/session.py)
              → PostgreSQL or SQLite

Key Modules

  • app/main.py — FastAPI app init, lifespan (calls init_db), CORS, router registration. Also hosts the GET /api/v1/apikeys/routes endpoint that lists all API-key-accessible routes.
  • app/auth.py — All auth dependency factories: jwt_required(), apikey_required(), apikey_or_jwt_required(), get_current_user, get_current_user_optional. JWT creation and validation lives here too.
  • app/config.pySettings via pydantic-settings, loaded from .env.
  • app/models.py — SQLAlchemy ORM models: User, Role (M2M via user_roles), APIKey, APIKeyPermission, PasswordResetToken, RefreshToken. All share the same Base.
  • app/schemas.py — Pydantic v2 schemas for all models.
  • app/utils.py — Password hashing (bcrypt), API key hashing (SHA-256), SendGrid email helper.
  • app/db/session.py — Async SQLAlchemy engine + AsyncSessionLocal. init_db() calls create_all on startup.
  • app/db/ — Plain async CRUD functions (no base class); one file per domain (users.py, apikeys.py, auth.py).

Authentication

Three dependency factories in app/auth.py:

Factory Header Enforces
jwt_required(required_roles=(...)) Authorization: Bearer <token> JWT validity + optional role check
apikey_required(route, method) X-API-Key: <key> Key active + per-route permission
apikey_or_jwt_required(route, method, required_roles) Either Rejects if both provided

JWT: access token (15 min) + refresh token in HttpOnly cookie (7 days). API keys are SHA-256 hashed at creation; the raw key is revealed only once (APIKeyRevealSchema).

Adding New Routes

See app/api/examples.py — it demonstrates all auth patterns (no auth, JWT, admin-only JWT, API key, either). Use it as a template:

router = APIRouter(prefix="/api/v1/myroutes", tags=["My Feature"])

@router.get("", dependencies=[Depends(apikey_or_jwt_required())])
async def my_endpoint(session: AsyncSession = Depends(get_async_session)):
    ...

Then register in app/main.py:

from app.api import myroutes
app.include_router(myroutes.router)

Database Backend

Controlled by DATABASE_URL in .env:

  • sqlite+aiosqlite:///./db.sqlite3 — default/development
  • postgresql+asyncpg://user:pass@host:port/db — production

Switching to TimescaleDB: Replace app/models.py with app/models.py.timescale and alembic/env.py with alembic/env.py.timescale. Docker profiles: timescale-db or sqlite-db.

API Routes (all prefixed /api/v1/)

Router Auth Purpose
auth None/Optional JWT login, refresh, password reset
apikeys JWT CRUD for API keys and permissions
profile JWT Current user profile and password
users None Username/email availability checks
examples Various Auth pattern reference (not for production)

Environment Variables

Key .env variables (generated by setup.sh):

DATABASE_URL=sqlite+aiosqlite:///./db.sqlite3
SECRET_KEY=<auto-generated>
ACCESS_TOKEN_EXPIRE_MINUTES=15
REFRESH_TOKEN_EXPIRE_DAYS=7
ENVIRONMENT=Development
FRONTEND_URL=http://localhost:5173
SENDGRID_API_KEY=          # Optional
SENDGRID_FROM_EMAIL=       # Optional
DEFAULT_ADMIN_USERNAME=admin
DEFAULT_ADMIN_PASSWORD=admin

API docs available at http://localhost:8000/docs when running.