Files
Project-SDE-WP-Suite/server/README.md
2026-06-15 13:46:43 -05:00

253 lines
7.2 KiB
Markdown

# 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=<strong-random-password>
# Must match POSTGRES_* above; hostname is the compose service name "db"
DATABASE_URL=postgresql+psycopg://wpsuite:<strong-random-password>@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
```