This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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).
# 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# 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.
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.
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
app/main.py— FastAPI app init, lifespan (callsinit_db), CORS, router registration. Also hosts theGET /api/v1/apikeys/routesendpoint 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.py—Settingsvia pydantic-settings, loaded from.env.app/models.py— SQLAlchemy ORM models:User,Role(M2M viauser_roles),APIKey,APIKeyPermission,PasswordResetToken,RefreshToken. All share the sameBase.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()callscreate_allon startup.app/db/— Plain async CRUD functions (no base class); one file per domain (users.py,apikeys.py,auth.py).
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).
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)Controlled by DATABASE_URL in .env:
sqlite+aiosqlite:///./db.sqlite3— default/developmentpostgresql+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.
| 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) |
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.