Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
811c48be77
commit
34a027e17b
@@ -0,0 +1,66 @@
|
|||||||
|
# portal
|
||||||
|
|
||||||
|
The question runtime behind [uhhm.no](https://uhhm.no). Every page,
|
||||||
|
form, review desk, and state machine it serves is declared in the
|
||||||
|
[`questions`](https://project.uhhm.no/uhhm/questions) content repo —
|
||||||
|
this codebase is the engine that renders, enforces, and records, and
|
||||||
|
it special-cases none of it.
|
||||||
|
|
||||||
|
## What the engine provides
|
||||||
|
|
||||||
|
- **Content-driven pages** (`src/content.rs`, `src/app.rs`): YAML
|
||||||
|
loaded from Gitea at boot and hot-swapped on a NATS reload signal;
|
||||||
|
a bad push keeps the last-good content serving. A page's `id` is
|
||||||
|
its URL; `qualifies` gates it to a Kanidm group.
|
||||||
|
- **State machines as content** (`src/aggregates/`): `aggregates.yaml`
|
||||||
|
declares each bucket's states and legal transitions; the engine
|
||||||
|
replays a record's event history and refuses undeclared moves, with
|
||||||
|
optimistic concurrency (CAS on the event log's sequence) against
|
||||||
|
racing decisions. An empty history reseeds from the KV projection,
|
||||||
|
so wiping the event stream strands nothing.
|
||||||
|
- **Durable events + projections** (`src/events/`, `src/answers.rs`):
|
||||||
|
every submission and decision appends to a JetStream event log and
|
||||||
|
projects into a NATS KV bucket pages read back; every one also
|
||||||
|
publishes on `portal.answers.submitted` for automations (n8n) to
|
||||||
|
react to.
|
||||||
|
- **Answer chains** (`src/chain.rs`): each submission hashes its
|
||||||
|
parent(s), so a visitor's path through the questions is a verifiable
|
||||||
|
lineage; `?chain=` links carry it, and self-service transitions
|
||||||
|
(unsubscribe) authorize by holding one.
|
||||||
|
- **Live resources** (`src/resource.rs`): content can pull a KV
|
||||||
|
bucket, Gitea starred/org repos, or any public JSON URL (SSRF
|
||||||
|
fail-closed), reshaped by a content-declared `jq` filter.
|
||||||
|
- **Review desks**: any Kv resource with `transitions` renders rows
|
||||||
|
with per-state action buttons and one shared confirm per
|
||||||
|
alternative — owners walk records through their graphs without
|
||||||
|
bespoke UI per bucket.
|
||||||
|
|
||||||
|
## Binaries
|
||||||
|
|
||||||
|
- `portal` — the server (Leptos SSR + hydrate, Axum underneath).
|
||||||
|
- `question_lint` — headless content validation, run by the
|
||||||
|
`questions` repo's CI against a prebuilt copy this repo's deploy
|
||||||
|
publishes; also works offline: `question_lint --path <dir>`.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo leptos build # full app (server + wasm)
|
||||||
|
cargo test --features ssr # engine tests
|
||||||
|
cargo build --features ssr --bin question_lint
|
||||||
|
```
|
||||||
|
|
||||||
|
Runtime configuration is env vars (see `src/main.rs` and
|
||||||
|
`.gitea/workflows/deploy.yml`): `NATS_URL`, `CONTENT_REPO`/
|
||||||
|
`CONTENT_BRANCH`, Kanidm OIDC (`KANIDM_URL`, `OAUTH2_CLIENT_*`),
|
||||||
|
optional `GARAGE_*` for uploads, `GITEA_API_TOKEN` for authenticated
|
||||||
|
resource pulls, `AUTOMATION_READ_TOKEN` for the automation KV read
|
||||||
|
endpoint.
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
Pushing `main` triggers `.gitea/workflows/deploy.yml` on the
|
||||||
|
bare-metal runner: release build, ship to
|
||||||
|
`/srv/app/uhhm-portal/releases/<sha>`, flip the `current` symlink,
|
||||||
|
restart `app@uhhm-portal`, reload Caddy. Content changes never come
|
||||||
|
through here — they hot-reload live from the `questions` repo.
|
||||||
Reference in New Issue
Block a user