feat(docs): per-application documentation, admin-authored + public (BUNYIP-388) #382

Merged
nrupard merged 3 commits from feat/BUNYIP-388-application-docs into main 2026-07-21 17:15:22 +02:00
Owner

What

Per-application documentation: multiple markdown pages per catalog app, authored by admins and read publicly, rendered through the same pipeline as the global /docs (markdown with raw HTML stripped). Mokosh Server and Mokosh Web ship with initial docs; every other app starts with none.

Layers

  • Data - new migration 20260721000010_create_application_docs.sql: application_docs (multi-page, application_id FK ON DELETE CASCADE, unique per app+slug, sort_order), seeded for mokosh-server (Getting Started + Configuration, grounded in its real env config) and mokosh-www (Getting Started). Validated on a throwaway postgres (seed counts, dollar-quoted bodies, cascade).
  • Domain + API - ApplicationDoc model + ApplicationDocRepository. Public reads GET /v1/applications/{slug}/docs and /{doc_slug} (no auth, 404 on unknown); admin writes (AdminUser) under /v1/admin/applications/{app_id}/docs and /v1/admin/application-docs/{doc_id}.
  • Web - public pages /apps/{slug}/docs (index) and /apps/{slug}/docs/{doc_slug} (page), reusing render_markdown + docs styling with an empty state and a 404 branch. Admin docs manager at /admin/applications/{id}/docs (list / add / edit / delete), linked from the app edit page; a Documentation link on each catalog card.

Verification

Workspace build + clippy + fmt green (SQLX_OFFLINE, pinned rust-builder); 107 bunyip-web tests pass; the migration and repository join queries validated on postgres.

Follow-up (not in scope)

A has_docs flag on the app response would let the catalog hide the Documentation link for apps that have no pages yet (today it links unconditionally and the index shows an empty state).

## What Per-application documentation: multiple markdown pages per catalog app, authored by admins and read publicly, rendered through the same pipeline as the global `/docs` (markdown with raw HTML stripped). Mokosh Server and Mokosh Web ship with initial docs; every other app starts with none. ## Layers - **Data** - new migration `20260721000010_create_application_docs.sql`: `application_docs` (multi-page, `application_id` FK ON DELETE CASCADE, unique per app+slug, `sort_order`), seeded for `mokosh-server` (Getting Started + Configuration, grounded in its real env config) and `mokosh-www` (Getting Started). Validated on a throwaway postgres (seed counts, dollar-quoted bodies, cascade). - **Domain + API** - `ApplicationDoc` model + `ApplicationDocRepository`. Public reads `GET /v1/applications/{slug}/docs` and `/{doc_slug}` (no auth, 404 on unknown); admin writes (`AdminUser`) under `/v1/admin/applications/{app_id}/docs` and `/v1/admin/application-docs/{doc_id}`. - **Web** - public pages `/apps/{slug}/docs` (index) and `/apps/{slug}/docs/{doc_slug}` (page), reusing `render_markdown` + docs styling with an empty state and a 404 branch. Admin docs manager at `/admin/applications/{id}/docs` (list / add / edit / delete), linked from the app edit page; a Documentation link on each catalog card. ## Verification Workspace build + clippy + fmt green (SQLX_OFFLINE, pinned rust-builder); 107 bunyip-web tests pass; the migration and repository join queries validated on postgres. ## Follow-up (not in scope) A `has_docs` flag on the app response would let the catalog hide the Documentation link for apps that have no pages yet (today it links unconditionally and the index shows an empty state).
feat(docs): per-application documentation, admin-authored + public (BUNYIP-388)
Some checks failed
E2E / Playwright against deployment (pull_request) Successful in 44s
Check / fmt + clippy + build + tests (pull_request) Has been cancelled
6fe190bf06
Adds documentation to each catalog application: multiple markdown pages per app, authored by admins and read publicly, rendered through the same pipeline as the global /docs (markdown with raw HTML stripped). Mokosh Server and Mokosh Web ship with initial docs; every other app starts with none.

Data: new migration `20260721000010_create_application_docs.sql` creates `application_docs` (multi-page, `application_id` FK ON DELETE CASCADE, unique per app+slug, `sort_order`) and seeds mokosh-server (Getting Started + Configuration, grounded in its real env config: DATABASE_URL, OIDC_ISSUER/OIDC_AUDIENCE, ENCRYPTION_KEY, CORS_ORIGIN, RUN_MIGRATIONS) and mokosh-www (Getting Started). Validated on a throwaway postgres: seed counts (server 2 / web 1 / others 0), dollar-quoted bodies intact, and cascade delete.

Domain + API: `ApplicationDoc` model + `ApplicationDocRepository` (public list/get by slug via a join, admin create/update/delete). Public reads `GET /v1/applications/{slug}/docs` and `/{doc_slug}` take no auth and 404 on an unknown app or page; admin writes (AdminUser) live under `/v1/admin/applications/{app_id}/docs` and `/v1/admin/application-docs/{doc_id}`.

Web (bunyip-web): public render pages at `/apps/{slug}/docs` (index) and `/apps/{slug}/docs/{doc_slug}` (page), reusing the existing render_markdown + docs styling, with an empty state and a 404 branch. An admin docs manager at `/admin/applications/{id}/docs` lists pages and supports add / edit / delete, linked from the app edit page ("Manage documentation"); each catalog card gets a Documentation link. Typed API client calls added in api/calls.rs (public) and api/admin.rs (admin).

Verified green: workspace build + clippy + fmt (SQLX_OFFLINE, pinned rust-builder); 107 bunyip-web tests pass; the migration and repository join queries validated on postgres. A `has_docs` flag to hide the catalog Documentation link for apps without pages is a natural follow-up.

#BUNYIP-388

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(docs): friendlier empty state for an app with no docs (BUNYIP-388)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 31s
Check / fmt + clippy + build + tests (pull_request) Successful in 9m59s
040603cc1f
Reword the /apps/{slug}/docs empty state from the flat "No documentation is available for this app yet." to a warmer "Sorry. No docs for this app yet :(".

#BUNYIP-388

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(docs): gate per-app doc reads, audit doc writes, robust sort_order (BUNYIP-388)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 43s
Check / fmt + clippy + build + tests (pull_request) Successful in 10m24s
Create release / Create release from merged PR (pull_request) Has been skipped
e3032c56e6
Review follow-up addressing three findings on the per-application docs feature.

Public reads now apply the same visibility gate as get_application via a shared gate_public_app helper: the app must be active, and a restricted product is hidden (404) from anyone who is not an admin or actively entitled. Previously the docs handlers served any app's pages with no gate, so documenting a restricted or inactive product would have leaked its existence and content publicly. The two public handlers now take OptionalUser and run the gate before serving.

Admin doc create / update / delete are now audit-logged, matching update_application: new AuditAction variants ApplicationDocCreated / ApplicationDocUpdated / ApplicationDocDeleted record the acting admin, the application_doc resource, and slug/title metadata. The audit action column is free text, so no migration is needed.

The admin sort_order form field is now a String parsed through validate::parse_i32 (empty becomes 0, non-numeric is handled) rather than deserialized straight to i32, so a cleared number input no longer 400s the whole form. This follows the existing GroupForm convention (BUNYIP-113).

Verified: workspace build + clippy + fmt green; bunyip-domain and bunyip-web tests pass.

#BUNYIP-388

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/BUNYIP-388-application-docs 2026-07-21 17:15:22 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!382
No description provided.