Skip to content

Chipmo-Sentry/sentry-backend

Repository files navigation

sentry-backend

HTTP API for Chipmo Sentry — auth, multi-tenant org/store/camera/alert CRUD, feedback, OpenAPI contract.

Python 3.11 · FastAPI · SQLAlchemy 2.0 async · Postgres · structlog · Apache 2.0


What this service does

  • Identity & multi-tenancy: User accounts, Organization → Store → Camera hierarchy, role-based access (owner/admin/staff + super-admin)
  • JWT auth: httpOnly cookie + Bearer fallback; 15 min access + 7 day refresh
  • Clip CRUD: receive uploaded mp4 clips, dispatch to sentry-ai for inference
  • Alert CRUD: store AI verdicts, expose via REST + SSE real-time push
  • Feedback: collect staff TP/FP marking, feed M3 auto-learner
  • OpenAPI contract: /openapi.json is the source of truth for sentry-frontend and Go agent codegen

Not in scope here: video ingest (see sentry-ingest), AI inference (see sentry-ai).


Quick start

# 1. Install uv (one-time)
pip install uv

# 2. Sync dependencies into ./.venv
uv sync

# 3. Copy env template, fill in secrets
cp .env.example .env
# Generate JWT_SECRET:          python -c "import secrets; print(secrets.token_urlsafe(48))"
# Generate SERVICE_TOKEN_SECRET: python -c "import secrets; print(secrets.token_urlsafe(48))"
# Generate RTSP_FERNET_KEY:     python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# 4. Spin up Postgres (Docker example)
docker run -d --name sentry-pg \
  -e POSTGRES_USER=sentry -e POSTGRES_PASSWORD=sentry -e POSTGRES_DB=sentry_dev \
  -p 5432:5432 postgres:16

# 5. Generate + apply initial migration (FIRST RUN ONLY — see "Migrations" below)
uv run alembic revision --autogenerate -m "initial schema"
uv run alembic upgrade head

# 6. Run the API
uv run uvicorn sentry_backend.main:app --reload
# → http://localhost:8000/healthz
# → http://localhost:8000/docs (Swagger UI)
# → http://localhost:8000/openapi.json (contract source of truth)

Project layout

src/sentry_backend/
├── main.py                 — FastAPI app, lifespan, middleware
├── settings.py             — pydantic-settings BaseSettings
├── logging_setup.py        — structlog (JSON in prod, pretty in dev)
├── security.py             — JWT, bcrypt, cookies, Fernet, service tokens
├── db/
│   ├── session.py          — async engine, AsyncSessionLocal
│   ├── base.py             — DeclarativeBase, UUIDPrimaryKeyMixin, TimestampMixin
│   └── models/             — ORM (one file per entity)
├── schemas/                — Pydantic request/response (OpenAPI source)
├── deps/                   — Dependency injection (get_db, get_current_user, ...)
├── repository/             — CRUD layer (service-callable)
├── services/               — Business logic
├── api/v1/                 — Versioned routers
└── api/stream.py           — SSE alert push endpoint

alembic/                    — Migration env + version files
tests/{unit,integration}/   — pytest suites

5-layer separation: Schema → ORM → Repository → Service → API. Routers do not write SQL.


Migrations

Alembic is async-aware and reads the database URL from settings.database_url (env), not from alembic.ini. So secrets never land in the repo.

# Generate a migration from ORM changes
uv run alembic revision --autogenerate -m "add foo column to bar"

# Inspect the generated file — autogenerate misses some changes (e.g. constraint renames)
# Edit alembic/versions/<id>_<slug>.py manually if needed

# Apply to current DB
uv run alembic upgrade head

# Roll back one step
uv run alembic downgrade -1

The very first migration is NOT committed in this repo. Run alembic revision --autogenerate -m "initial schema" against a fresh Postgres to generate it, then commit. This avoids shipping a migration that hasn't been verified against a real DB.


Testing

# All tests
uv run pytest

# Unit only (fast, no DB)
uv run pytest tests/unit/

# Integration (needs real Postgres — DATABASE_URL pointed at a test DB)
uv run pytest tests/integration/

# With coverage report
uv run pytest --cov=sentry_backend --cov-report=html

Lint + type-check

uv run ruff format .
uv run ruff check .
uv run mypy src/sentry_backend

CI runs all three; PR fails if any of them does.


Deployment

Target: Railway Pro. Postgres is a Railway addon. CI lives in .github/workflows/ci.yml (ruff + mypy strict + pytest).

One-time Railway setup

  1. Create a new Railway project from this GitHub repo (Railway auto-detects Dockerfile + railway.toml).

  2. Add the Postgres addon → DATABASE_URL is wired automatically.

  3. Set these env vars in the Railway dashboard:

    Var How to generate
    JWT_SECRET python -c "import secrets; print(secrets.token_urlsafe(48))"
    SERVICE_TOKEN_SECRET same
    RTSP_FERNET_KEY python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
    ALLOWED_ORIGINS comma-separated, e.g. https://app.sentry.chipmo.mn,https://admin.sentry.chipmo.mn
    ENVIRONMENT production
    LOG_LEVEL INFO
  4. Trigger a deploy. Container CMD runs alembic upgrade head then starts uvicorn.

  5. Expose a public domain (Railway → Settings → Networking) and CNAME api.sentry.chipmo.mn to it.

First-run: generate the initial migration

The repo intentionally ships no initial migration (see Migrations). Once Railway Postgres is reachable from your local machine:

# Get DATABASE_URL from Railway (Settings → Variables → DATABASE_URL)
export DATABASE_URL="postgresql+asyncpg://..."
uv run alembic revision --autogenerate -m "initial schema"
# Inspect alembic/versions/<id>_initial_schema.py, then commit + push.
# Railway's next deploy will run `alembic upgrade head` automatically.

Local Docker build (smoke test)

docker build -t sentry-backend:dev .
docker run --rm -p 8000:8000 --env-file .env sentry-backend:dev
curl http://localhost:8000/healthz

Related repos

Platform overview: Sentry-v.3 README (local workspace)

About

Chipmo Sentry HTTP API (Python FastAPI + Postgres)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages