# Smash or Pass — Backend FastAPI + SQLAlchemy (SQLite) + boto3 (MinIO/S3). Serves the API consumed by [`sop-front`](../sop-front/). See [`../CLAUDE.md`](../CLAUDE.md) for full architecture notes. --- ## Stack - Python 3.12, FastAPI, Uvicorn - SQLAlchemy 2 (sync) + SQLite - Pydantic v2 + `pydantic-settings` - `boto3` for S3-compatible object storage (MinIO) --- ## Project layout ``` sop-back/ ├── app/ │ ├── main.py # FastAPI factory + lifespan (DB + bucket init) │ ├── core/{config,deps}.py # Settings, admin gate │ ├── db/database.py # Engine, SessionLocal, get_db, Base │ ├── models/models.py # Collection, Character │ ├── schemas/schemas.py # Pydantic schemas │ ├── services/storage.py # MinIO upload/delete, bucket bootstrap │ └── api/routes/ │ ├── health.py # /health, /admin/status │ ├── collections.py # /collections │ └── admin.py # /admin/* (require_admin) ├── requirements.txt ├── Dockerfile └── .env.example ``` --- ## Configuration Copy `.env.example` to `.env` and adjust: | Var | Default | Notes | |---|---|---| | `ADMIN_ENABLED` | `false` | When `true`, `/admin/*` routes are exposed and the frontend renders the admin panel | | `ALLOWED_ORIGINS` | `["*"]` | CORS, JSON array string | | `DATABASE_URL` | `sqlite:///./data/sop.db` | SQLite file path | | `S3_ENDPOINT_URL` | `http://localhost:9000` | What the **backend** uses to reach MinIO | | `S3_PUBLIC_URL` | `http://localhost:9000` | What gets stored in `s3_url` and dereferenced by the **browser** | | `S3_ACCESS_KEY` / `S3_SECRET_KEY` | `minioadmin` / `minioadmin` | Credentials | | `S3_BUCKET` | `sop` | Auto-created on startup, set public-read | | `S3_REGION` | `us-east-1` | Required by boto3 | > **Security note:** `ADMIN_ENABLED=true` exposes admin endpoints to anyone who can reach the backend. There is no user auth — by design, per project spec. In production, either (a) put the backend behind an authenticated reverse proxy / VPN, (b) keep `ADMIN_ENABLED=false` outside of admin sessions, or (c) replace the gate with proper auth. --- ## Local development You need a running MinIO. Easiest path: spin it up via Docker. ```bash # from repo root docker compose up -d minio minio-init ``` Then run the backend natively: ```bash cd sop-back python -m venv .venv source .venv/bin/activate pip install -r requirements.txt cp .env.example .env # edit .env: set ADMIN_ENABLED=true if you want to upload uvicorn app.main:app --reload ``` - API: http://localhost:8000 - Interactive docs: http://localhost:8000/docs - Health: http://localhost:8000/health The SQLite DB file is created at `sop-back/data/sop.db` on first startup; tables are auto-created. There is no Alembic — if you change models, delete the file during dev. --- ## API surface | Method | Path | Auth | Purpose | |---|---|---|---| | GET | `/health` | — | Liveness | | GET | `/admin/status` | — | `{admin_enabled: bool}` | | GET | `/collections` | — | List collections (with `character_count`) | | GET | `/collections/{id}` | — | Collection + characters | | POST | `/admin/collections` | admin | multipart `name` + `files[]` | | POST | `/admin/collections/{id}/characters` | admin | multipart `files[]` | | DELETE | `/admin/collections/{id}` | admin | Delete collection + S3 objects | | DELETE | `/admin/characters/{id}` | admin | Delete one character + its S3 object | Allowed image types: `image/jpeg`, `image/png`, `image/webp`, `image/gif`. --- ## Production deployment ### Docker (recommended) The repo-root `docker-compose.yml` already wires backend + MinIO + bucket init. From the repo root: ```bash docker compose up -d --build backend minio minio-init ``` For a real deployment you should override these defaults: 1. **Use long, random MinIO credentials.** Edit the `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` env on the `minio` service and the matching `S3_ACCESS_KEY` / `S3_SECRET_KEY` on the backend. 2. **Set `S3_PUBLIC_URL` to the public hostname** browsers will hit (e.g. `https://media.example.com`). Put MinIO behind a TLS-terminating reverse proxy on that hostname. 3. **Set `ALLOWED_ORIGINS`** to the exact frontend origin(s) — never `["*"]` in prod. 4. **Persist volumes**: `minio-data` and `backend-data` (the SQLite file) — already declared in compose. Back them up. 5. **Run behind a reverse proxy** (Caddy / Traefik / nginx) terminating TLS in front of port 8000. 6. Consider switching `DATABASE_URL` to PostgreSQL if you expect concurrent writes. ### Without Docker Same install steps as local, but: ```bash pip install -r requirements.txt gunicorn gunicorn app.main:app -k uvicorn.workers.UvicornWorker -w 2 -b 0.0.0.0:8000 ``` Run under systemd or a supervisor of your choice. Front it with nginx/Caddy for TLS. --- ## Schema migrations There are none yet — `Base.metadata.create_all` runs on startup. If the schema changes incompatibly, either: - Wipe `data/sop.db` (dev), or - Add Alembic before the next prod deploy.