# Work Package Suite API A small Python (FastAPI) service that stores project **SOPs**, **Work Packages**, and **comments** in PostgreSQL. NGINX serves the static site and proxies `/api/` to this service. ``` browser → NGINX ──serves──> static site (index.html, …) └─proxy /api/─> api container (:8000) → db container (postgres) ``` ## Endpoints | Method | Path | Purpose | |--------|------|---------| | GET | `/api/health` | liveness check | | POST | `/api/sops` | create/update a SOP (upsert by `id`) | | GET | `/api/sops` | list SOP summaries | | GET | `/api/sops/latest?complete=true` | most recent (complete) SOP | | GET | `/api/sops/{id}` | full SOP document | | DELETE | `/api/sops/{id}` | delete a SOP | | POST | `/api/wps` | create/update a Work Package (upsert by `id`) | | GET | `/api/wps?sop_id=…` | list WPs (optionally for one SOP) | | GET | `/api/wps/{id}` | full WP document | | DELETE | `/api/wps/{id}` | delete a WP | | POST | `/api/comments` (and `/api/feedback`) | add a comment | | GET | `/api/comments?source=&sop_id=&wp_id=&step=` | list comments | Interactive docs once running: **`/api/docs`**. The full client document is stored in each row's `data` (JSON) column; common fields (name, number, status, …) are promoted to columns for listing/filtering. --- ## Local dev ```bash cd server python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r requirements.txt # No DATABASE_URL → uses a local sqlite file, so you can start immediately: uvicorn server.app:app --reload --port 8000 # run from the PROJECT ROOT ``` Then open http://localhost:8000/api/docs. > Run uvicorn/gunicorn from the **project root** (the folder that contains the > `server/` directory), because the import path is `server.app:app`. --- ## Production — Docker Compose This is the recommended production setup. Three containers run in an isolated internal network; only NGINX is exposed to the outside via the external `proxy` network. ``` [external proxy network] │ ┌────▼────┐ internal network ┌──────────┐ ┌────────┐ │ nginx │ ───────────────────> │ api │ → │ db │ └─────────┘ └──────────┘ └────────┘ ``` ### 1. Create the credentials file Create `.env` in the **project root** (same directory as `docker-compose.yml`). This file is never committed — add it to `.gitignore`. ```bash # .env — project root POSTGRES_DB=wpsuite POSTGRES_USER=wpsuite POSTGRES_PASSWORD= # Must match POSTGRES_* above; hostname is the compose service name "db" DATABASE_URL=postgresql+psycopg://wpsuite:@db:5432/wpsuite ``` Generate a strong password: ```bash openssl rand -base64 32 ``` ### 2. Add the Dockerfile Create `Dockerfile` in the **project root**: ```dockerfile FROM python:3.12-slim WORKDIR /app COPY server/requirements.txt ./server/ RUN pip install --no-cache-dir -r server/requirements.txt COPY server/ ./server/ EXPOSE 8000 CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", \ "-b", "0.0.0.0:8000", "--workers", "2", "server.app:app"] ``` ### 3. Update the NGINX site config The API is no longer at `127.0.0.1:8000` — it is the `api` container. Update the `/api/` proxy block in your nginx conf (e.g. `nginx/conf.d/wp-suite.conf`): ```nginx location /api/ { proxy_pass http://api:8000; # ← service name, not localhost proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; client_max_body_size 5m; } ``` ### 4. docker-compose.yml Replace your existing `docker-compose.yml` with: ```yaml services: webserver: image: nginx:alpine container_name: nginx_webserver volumes: - ./html:/usr/share/nginx/html:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./logs:/var/log/nginx restart: unless-stopped depends_on: api: condition: service_started networks: - proxy # external — reachable by your reverse proxy / traefik - internal # needs a path to the api container api: build: . container_name: wp_api env_file: .env # loads DATABASE_URL restart: unless-stopped depends_on: db: condition: service_healthy # waits for postgres to accept connections networks: - internal db: image: postgres:16-alpine container_name: wp_db env_file: .env # loads POSTGRES_DB / USER / PASSWORD volumes: - pgdata:/var/lib/postgresql/data restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 networks: - internal volumes: pgdata: networks: proxy: name: proxy external: true internal: internal: true # no outbound internet access from api/db ``` ### 5. First-time startup ```bash # Build the api image and start all containers docker compose up -d --build # Confirm all three containers are running docker compose ps # Tail logs (Ctrl-C to stop following) docker compose logs -f api ``` Tables are created automatically on first API startup — no manual `CREATE TABLE` needed. ### Authentication notes **Postgres → API authentication** is handled entirely through `DATABASE_URL` in `.env`. The `db` container uses `POSTGRES_USER` / `POSTGRES_PASSWORD` to initialise the database on first run; the `api` container uses the matching credentials in `DATABASE_URL` to connect. Neither credential ever appears in the compose file itself. **Network isolation**: the `db` container is on the `internal` network only — it has no port exposed to the host and is unreachable from outside the compose stack. Only the `api` container can open a connection to it. **Changing the password**: update both `POSTGRES_PASSWORD` and the password in `DATABASE_URL` in `.env`, then: ```bash # Stop api first (db must keep running to accept the ALTER USER command) docker compose stop api docker compose exec db psql -U wpsuite -c "ALTER USER wpsuite PASSWORD 'new-password';" docker compose start api ``` ### Day-to-day operations ```bash # Rebuild api after a code change docker compose up -d --build api # View postgres data directly docker compose exec db psql -U wpsuite -d wpsuite # Take a database backup docker compose exec db pg_dump -U wpsuite wpsuite > backup-$(date +%F).sql # Restore from backup docker compose exec -T db psql -U wpsuite -d wpsuite < backup-2025-01-01.sql # Stop everything (data volume is preserved) docker compose down # Stop everything AND delete all data docker compose down -v ``` --- ## Quick test ```bash curl -X POST http://127.0.0.1:8000/api/comments \ -H 'Content-Type: application/json' \ -d '{"type":"home_feedback","name":"Test","text":"hello"}' curl http://127.0.0.1:8000/api/comments ``` Or via the nginx proxy (replace with your hostname): ```bash curl https://wp-suite.company.local/api/health ```