Add Python/FastAPI + PostgreSQL backend (Phase 1)
- server/: FastAPI app with SQLAlchemy models for sops, work_packages, comments - Endpoints for SOP/WP upsert+list+get+delete and comment create+list; /api/feedback kept as an alias so the existing client keeps working - Portable across engines (PostgreSQL prod, SQLite dev fallback) - requirements.txt, .env.example, and server/README.md (Postgres + systemd) - NGINX now proxies /api/ to the API (replaces the Power Automate hop; comments persist to SQL) - Rewrite DEPLOYMENT.md for the API + database architecture - Add .gitignore for venv/.env/sqlite Phase 2 (wire the client apps to the API) is next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
187
DEPLOYMENT.md
187
DEPLOYMENT.md
@@ -1,153 +1,57 @@
|
||||
# Deployment & Feedback Collection
|
||||
# Deployment
|
||||
|
||||
The Work Package Suite is a **static client-side app** — plain HTML, CSS, and
|
||||
JavaScript. There is no build step and no database.
|
||||
The Work Package Suite has two parts:
|
||||
|
||||
## Hosting it behind the firewall
|
||||
- a **static front end** (plain HTML/CSS/JS — no build step), and
|
||||
- a **Python API** (FastAPI) backed by **PostgreSQL**, which stores the project
|
||||
SOPs, Work Packages, and comments so they are shared across users instead of
|
||||
living in each person's browser.
|
||||
|
||||
Copy the whole folder to any internal web server and serve it over HTTP(S):
|
||||
|
||||
- **IIS / Apache / nginx** — drop the files in the site root. `index.html` is the
|
||||
entry point.
|
||||
- **SharePoint / network share** — works too, as long as the files are served
|
||||
over `http(s)://` (not opened as `file://...`). Serving over HTTP makes
|
||||
`localStorage` and the embedded Work Package Creator (an `<iframe>`) behave
|
||||
reliably.
|
||||
|
||||
The app makes **no outbound internet calls** — the logo and all scripts are
|
||||
local, and the previous Google-Fonts dependency has been removed (fonts now fall
|
||||
back to system UI fonts). So it runs fully air-gapped behind a corporate
|
||||
firewall.
|
||||
|
||||
## Where data lives
|
||||
|
||||
By default **everything is stored in each user's own browser** (`localStorage`):
|
||||
the SOP configuration, the saved Work Packages, the usage logs, and all feedback
|
||||
/ comments. This means:
|
||||
|
||||
- Data is **per-user and per-device** — it is not shared between people, and
|
||||
clearing browser data erases it.
|
||||
- Nothing is transmitted anywhere unless you enable central collection (below).
|
||||
|
||||
## Feedback collection
|
||||
|
||||
There are two layers, and they work together.
|
||||
|
||||
### 1. Export / Import (no server required — works today)
|
||||
|
||||
Every feedback surface has **Export** and **Import** buttons:
|
||||
|
||||
- Home page → *Leave Feedback* panel
|
||||
- SOP Configuration → *Step Comments* (header button)
|
||||
- Work Package Creator → *Comments* drawer
|
||||
|
||||
A reviewer clicks **Export** to download a JSON file and sends it to you; you
|
||||
click **Import** on your machine to merge everyone's feedback together (imports
|
||||
de-duplicate, so re-importing is safe). This needs zero infrastructure and works
|
||||
behind any firewall.
|
||||
|
||||
### 2. Central auto-collection (optional — flip on when hosting is known)
|
||||
|
||||
To also gather every submission automatically into one place, set a single value
|
||||
in [`feedback-config.js`](feedback-config.js):
|
||||
|
||||
```js
|
||||
window.FEEDBACK_ENDPOINT = 'https://your-endpoint-url';
|
||||
```
|
||||
browser → NGINX ──serves──> static site (index.html, …)
|
||||
└─proxy /api/─> Python API (uvicorn/gunicorn :8000) → PostgreSQL
|
||||
```
|
||||
|
||||
When set, each submission is additionally `POST`ed as JSON to that URL (saving
|
||||
locally still happens, so a failed/disabled endpoint never loses feedback). The
|
||||
endpoint can be either of:
|
||||
Everything runs inside your firewall; the app makes **no outbound internet
|
||||
calls** (the logo and scripts are local and the old Google-Fonts dependency was
|
||||
removed).
|
||||
|
||||
#### Option A — a small backend on your host
|
||||
Any server that can run code and append the request body to a file you can
|
||||
download. Example (Node/Express):
|
||||
## 1. Front end (NGINX)
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.post('/feedback', (req, res) => {
|
||||
fs.appendFileSync('feedback.jsonl', JSON.stringify(req.body) + '\n');
|
||||
res.sendStatus(204);
|
||||
});
|
||||
app.listen(8080);
|
||||
```
|
||||
Copy the project files to a web root and serve them over HTTPS. The provided
|
||||
[`nginx-wp-suite.conf`](nginx-wp-suite.conf) serves the static files and proxies
|
||||
`/api/` to the Python API. Set `server_name`, the `ssl_certificate` paths, and
|
||||
`root`, then `sudo nginx -t && sudo systemctl reload nginx`.
|
||||
|
||||
Each line of `feedback.jsonl` is one submission; download it anytime. (PHP/
|
||||
Python/ASP.NET equivalents are a few lines too.)
|
||||
Serving over real HTTP(S) (not `file://`) also makes the embedded Work Package
|
||||
Creator (`<iframe>`) and any browser-side caching behave reliably.
|
||||
|
||||
#### Option B — Internal NGINX reverse proxy → Power Automate (the chosen setup)
|
||||
## 2. API + database
|
||||
|
||||
The browser posts to a **same-origin** path `/api/feedback`; NGINX forwards that
|
||||
to the Power Automate trigger. This avoids CORS entirely and keeps the secret
|
||||
trigger URL off the client. `FEEDBACK_ENDPOINT` is already set to
|
||||
`/api/feedback`, and [`nginx-wp-suite.conf`](nginx-wp-suite.conf) contains the
|
||||
full server block.
|
||||
Full setup — PostgreSQL, the systemd service, and the endpoint reference — is in
|
||||
[`server/README.md`](server/README.md). In short:
|
||||
|
||||
**On the NGINX box (one-time, server admin):**
|
||||
1. Copy the project files to the web root (e.g. `/var/www/wp-suite`).
|
||||
2. Install [`nginx-wp-suite.conf`](nginx-wp-suite.conf) (e.g. into
|
||||
`/etc/nginx/conf.d/`), and set `server_name`, the `ssl_certificate` paths
|
||||
(internal cert), and the `root`.
|
||||
3. In the `location = /api/feedback` block, replace the `proxy_pass` URL and the
|
||||
`Host` header with your real trigger URL / region host (keep the full query
|
||||
string incl. `sig=`).
|
||||
4. `sudo nginx -t && sudo systemctl reload nginx`.
|
||||
1. Create the `wpsuite` Postgres database/user.
|
||||
2. `pip install -r server/requirements.txt` into a venv.
|
||||
3. Set `DATABASE_URL` and run the API as a systemd service on `127.0.0.1:8000`.
|
||||
4. Tables are created automatically on first start.
|
||||
|
||||
> Two NGINX details that matter: `proxy_ssl_server_name on;` (SNI is required for
|
||||
> `*.logic.azure.com` or the TLS handshake fails) and the `Host` header set to
|
||||
> the Azure region host. Both are already in the provided config.
|
||||
Interactive API docs are at `/api/docs` once it's running.
|
||||
|
||||
**In Power Automate:**
|
||||
1. Create a flow with the **"When an HTTP request is received"** trigger.
|
||||
2. Set its **Request Body JSON Schema** to:
|
||||
## 3. Comments / feedback
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"app": { "type": "string" },
|
||||
"page": { "type": "string" },
|
||||
"submittedAt": { "type": "string" },
|
||||
"type": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"author": { "type": "string" },
|
||||
"text": { "type": "string" },
|
||||
"step": { "type": "integer" },
|
||||
"view": { "type": "string" },
|
||||
"timestamp": { "type": "string" },
|
||||
"ts": { "type": "string" },
|
||||
"id": { "type": "string" },
|
||||
"clientId": { "type": "string" }
|
||||
}
|
||||
}
|
||||
```
|
||||
> `name` is used by the home/SOP forms, `author` by the Work Package Creator.
|
||||
> Map both into one "Submitted by" column with an expression like
|
||||
> `coalesce(triggerBody()?['name'], triggerBody()?['author'])`.
|
||||
3. Add an action — **Create item** (SharePoint list) or **Add a row into a
|
||||
table** (Excel / Dataverse) — mapping the fields above.
|
||||
4. Save; copy the generated **HTTP POST URL** into `web.config`
|
||||
(`POWER_AUTOMATE_TRIGGER_URL`).
|
||||
5. A Power App (or just the list/Excel) reads that store to show live comments.
|
||||
Every feedback surface (home *Leave Feedback*, SOP *Step Comments*, WP *Comments*)
|
||||
posts to `/api/feedback`, which the API stores in the `comments` table. The
|
||||
**Export / Import** buttons remain as an offline fallback — a reviewer can export
|
||||
a JSON file and someone can import/merge it — but with the API running, comments
|
||||
are collected centrally with no manual steps.
|
||||
|
||||
Chain: `browser → /api/feedback (NGINX proxy) → Power Automate → SharePoint/Dataverse → Power App`.
|
||||
> The earlier Power Automate route is **no longer needed** — comments go straight
|
||||
> to Postgres. If you still want a Power App view, point a Power App at the
|
||||
> Postgres `comments` table via the on-prem data gateway, or have a flow read the
|
||||
> table; no change to this app is required.
|
||||
|
||||
The "downloadable file" is then just the Excel/SharePoint list, viewable live or
|
||||
exported — all inside your corporate cloud.
|
||||
|
||||
### CORS note
|
||||
If the endpoint is on a **different origin** than the site, it must return CORS
|
||||
headers allowing the site's origin (e.g.
|
||||
`Access-Control-Allow-Origin: https://wp-suite.yourcompany.local`). A Power
|
||||
Automate HTTP trigger and a same-host backend both handle this cleanly; a
|
||||
same-origin backend needs no CORS at all.
|
||||
|
||||
## Feedback payload shape
|
||||
|
||||
Each POST body looks like:
|
||||
### Comment payload shape
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -161,4 +65,17 @@ Each POST body looks like:
|
||||
}
|
||||
```
|
||||
|
||||
`type` is one of `home_feedback`, `sop_step_comment`, or `wp_review_comment`.
|
||||
`type` is one of `home_feedback`, `sop_step_comment`, or `wp_review_comment`. The
|
||||
API maps `name`/`author` → the comment author and keeps any extra fields in the
|
||||
row's `extra` JSON column.
|
||||
|
||||
## Data model (PostgreSQL)
|
||||
|
||||
| Table | Holds | Key columns |
|
||||
|-------|-------|-------------|
|
||||
| `sops` | project SOP baselines | `name`, `number`, `complete`, `data` (full SOP JSON) |
|
||||
| `work_packages` | individual IWPs | `sop_id`, `number`, `subject`, `type`, `status`, `data` (full WP JSON) |
|
||||
| `comments` | feedback from any page | `source`, `sop_id`, `wp_id`, `step`, `author`, `text` |
|
||||
|
||||
The complete client document is stored verbatim in each row's `data` column;
|
||||
frequently-listed fields are promoted to real columns for filtering.
|
||||
|
||||
Reference in New Issue
Block a user