docs(examples): add the CMS proof-of-concept dogfooding artifact
A WordPress-inspired CMS whose entire backend is PiCloud Rhai scripts deployed with pic apply, plus a SvelteKit frontend. Built to dogfood the project tool, CLI, and SDK; exercises docs/kv/files/users, docs+queue+cron +pubsub triggers, the transactional-outbox notification chain, a docs before-interceptor, set_if CAS, SSE, per-app CORS, env overlays, and a durable Workflow (validate -> enrich || seo -> publish -> finalize) started with workflow::start and polled with workflow::run_status, visualized live with Svelte Flow. FINDINGS.md / SECURITY.md capture every gap, surprise, and security issue found (each tagged [PiCloud] vs [CMS]); the platform fixes for the actionable ones ship in the preceding commit. Also folds in the read-only `pic` allowlist entries added to .claude/settings.json during the session (fewer-permission-prompts). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
96
examples/cms-poc/README.md
Normal file
96
examples/cms-poc/README.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# PiCloud CMS PoC — a WordPress-inspired CMS on PiCloud
|
||||
|
||||
A proof-of-concept content-management system whose **entire backend is PiCloud
|
||||
Rhai scripts**, deployed declaratively with `pic apply`, plus a SvelteKit
|
||||
frontend. Built to **dogfood the PiCloud project tool, CLI, and SDK** — the
|
||||
gaps/surprises/issues found along the way are in [FINDINGS.md](FINDINGS.md) and
|
||||
the security audit is in [SECURITY.md](SECURITY.md).
|
||||
|
||||
> This is a **test artifact**, not a production CMS. It deliberately exercises
|
||||
> as many PiCloud features as fit a blog, and records where they helped or hurt.
|
||||
|
||||
## What it does (WordPress-style)
|
||||
|
||||
| Feature | PiCloud pieces used |
|
||||
|---|---|
|
||||
| Admin dashboard + admin/author users | `users::` app-users + roles (`admin`/`author`/`reader`), a shared `auth` **module** as the access boundary |
|
||||
| Post management + editor | `docs` collection `posts`, `kv` slug index, `kv::set_if` **CAS** view counter |
|
||||
| Page management + editor | `docs` collection `pages` (one method-dispatched script on many routes) |
|
||||
| Post comments + moderation | `docs` collection `comments`, a **docs `before` interceptor** that HTML-escapes + sets moderation status |
|
||||
| Reader users | `users::` register/login/verify, `kv` rate-limiter |
|
||||
| Reader email on new posts | `docs` trigger → **durable queue** → `email::send_html` → **Mailpit** |
|
||||
| Post tags | `docs` tag arrays + a tag-cloud endpoint |
|
||||
| Scheduled publishing | **cron** trigger flips `scheduled`→`published` (which re-fires the notify chain) |
|
||||
| Media uploads | `files` collection (base64-in-JSON, image allowlist) |
|
||||
| Live comments | `pubsub::publish_durable` → **realtime SSE** (public topic) → browser `EventSource` |
|
||||
| Editorial automation | **Workflow** (durable DAG: validate → enrich ∥ seo → publish → finalize) started with `workflow::start`, polled with `workflow::run_status`; live-visualized with **Svelte Flow** in the admin dashboard |
|
||||
| Config / secrets / envs | `vars` (+ `picloud.dev/prod.toml` **overlays**), `secrets` (`setup-token`) |
|
||||
| Deploy | `[project]` + `[app]`, declarative `pic plan/apply --dir --env`, `--prune` |
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
picloud.toml # the whole backend: scripts, routes, triggers, interceptor, vars, secrets
|
||||
picloud.dev.toml # dev env overlay picloud.prod.toml # prod env overlay
|
||||
scripts/**/*.rhai # ~30 Rhai scripts (endpoints, modules, triggers, interceptor)
|
||||
run-server.sh # boots picloud on :8081 against the picloud_cms DB, relaying mail to Mailpit
|
||||
docker-compose.override.yml # Mailpit
|
||||
frontend/ # SvelteKit SPA (public site + admin dashboard)
|
||||
FINDINGS.md SECURITY.md
|
||||
```
|
||||
|
||||
## Run it
|
||||
|
||||
```sh
|
||||
# 0. infra: Postgres (repo compose) + Mailpit
|
||||
docker compose up -d postgres # from repo root
|
||||
docker run -d --name cms-mailpit -p 127.0.0.1:1025:1025 -p 127.0.0.1:8025:8025 \
|
||||
-e MP_SMTP_AUTH_ACCEPT_ANY=true axllent/mailpit:latest
|
||||
docker exec picloud-postgres-1 psql -U picloud -d picloud -c "CREATE DATABASE picloud_cms"
|
||||
|
||||
# 1. build + boot picloud (all-in-one) on :8081, relaying mail to Mailpit
|
||||
cargo build -p picloud
|
||||
bash examples/cms-poc/run-server.sh & # see the script for the exact env
|
||||
|
||||
# 2. deploy the CMS (from examples/cms-poc/)
|
||||
cd examples/cms-poc
|
||||
pic login --url http://localhost:8081 # admin / admin
|
||||
pic apps create cms --name "PiCloud CMS" # apply can't create apps (FINDINGS F-006)
|
||||
pic apps domains rm default <id> && pic apps domains add cms localhost # claim the host (F-009)
|
||||
echo -n "cms-setup-secret-123" | pic secrets set --app cms setup-token
|
||||
pic apply --dir . --env dev
|
||||
|
||||
# 3. bootstrap the first admin (no CLI to grant app-user roles — FINDINGS F-012)
|
||||
curl -X POST localhost:8081/cms/auth/bootstrap -H 'Content-Type: application/json' \
|
||||
-d '{"setup_token":"cms-setup-secret-123","email":"admin@cms.test","password":"adminpass1","display_name":"Site Admin"}'
|
||||
|
||||
# 4. frontend
|
||||
cd frontend && npm install && npm run dev # http://localhost:5173
|
||||
```
|
||||
|
||||
Then: register a reader, write+publish a post as admin, watch the email land in
|
||||
Mailpit (http://localhost:8025), comment on the post, approve it, and see it
|
||||
appear live.
|
||||
|
||||
## Backend API (all under `/cms/`, since `/api/` is reserved — FINDINGS F-007)
|
||||
|
||||
- Public: `GET /cms/posts`, `GET /cms/posts/:slug`, `GET /cms/posts/:id/comments`,
|
||||
`POST /cms/posts/:id/comments`, `GET /cms/pages`, `GET /cms/pages/:slug`,
|
||||
`GET /cms/tags`, `GET /cms/media/:id`, `POST /cms/auth/{register,login,logout}`, `GET /cms/auth/me`
|
||||
- Author+: `GET/POST /cms/admin/posts`, `PUT/DELETE /cms/admin/posts/:id`, `POST /cms/admin/media`
|
||||
- Admin: `/cms/admin/pages*`, `/cms/admin/comments*`, `/cms/admin/users*`
|
||||
|
||||
## Headlines from the audit
|
||||
|
||||
- 🟢 **Strengths:** the event model (docs/queue/cron/pubsub triggers), the
|
||||
transactional-outbox notification chain, `set_if` CAS, the docs `before`
|
||||
interceptor for write-time sanitization, `users::` + roles, SSE, and the
|
||||
declarative `plan/apply/--prune/--env` loop all worked well.
|
||||
- 🟠 **Rough edges:** `pic apply` can't create an app (only groups); `/api/` is a
|
||||
reserved route prefix; `throw #{statusCode}` becomes a 502 that leaks script
|
||||
internals; the docs filter DSL can't match array membership; user routes are
|
||||
JSON-only (no form/binary bodies); no CORS; no CLI to grant app-user roles.
|
||||
- 🔴 **Security root cause:** a public route holds **full app authority** and
|
||||
there's **no per-row authz** — every check is app code — while uncaught errors
|
||||
return a **502 that leaks the app id + script structure**. Get one `guard()`
|
||||
wrong and it's a silent full bypass. See [SECURITY.md](SECURITY.md).
|
||||
Reference in New Issue
Block a user