Reference example web app: end-to-end expense log (umbrella) #22

Open
opened 2026-04-28 08:13:21 +00:00 by mprihoda · 0 comments
mprihoda commented 2026-04-28 08:13:21 +00:00 (Migrated from github.com)

Reference example web app: end-to-end expense log

Why

iw-support exposes many ideas (security wiring, i18n + message catalogue, Tapir endpoints, HTMX/Vite/WebAwesome integration, ReBAC authorization, forms, codecs, scenarios, e2e testing) but downstream projects have to piece them together from guides and prior projects. We need one canonical, runnable, tested reference application that exercises these pieces together so we can:

  • demonstrate the intended way to use them,
  • catch integration regressions in CI rather than in downstream projects,
  • give new projects a copy-and-adapt starting point,
  • pressure-test our own APIs by building against them.

This issue is the umbrella spec for that reference app. Implementation will be split into follow-up issues/PRs.

Scope: shared expense log

A small multi-user expense tracker. Chosen because it naturally exercises i18n (currency, dates, decimal/thousands separators), authorization (group membership, ownership, admin override), forms + validation (amount, date, category), and dynamic UI (filters, inline edit, pagination) without being contrived.

Functional outline

  • Groups — a user belongs to one or more groups. Each group has members and at least one admin.
  • Expenses — recorded against a group. Fields: amount + currency, date, payer (group member), category, description (optional), tags (optional).
  • Permissions
    • any group member can list expenses in groups they belong to,
    • members can create expenses and edit/delete their own,
    • group admins can edit/delete any expense in the group,
    • non-members see nothing.
  • Views
    • login (OIDC redirect),
    • groups overview,
    • group detail with filterable expense table (month, category, payer),
    • inline new-expense form / inline edit,
    • per-month summary (total per category, per payer),
    • settings page (user profile, language switch).
  • Languages — Czech and English. All UI strings, dates, numbers, and currency formatted via the message catalogue.

Module coverage (what we're exercising)

The example must end up using each of the following, with a short note in the docs about why each is wired in:

  • core — base types, error handling, ZIO layers
  • codecs — JSON codecs for the API surface
  • entity — domain entities (Group, Expense, Membership)
  • forms — expense form, validation, error rendering
  • tapir (jvm + shared) — endpoint definitions, OpenAPI
  • server/http — HTTP server bootstrap, routes, error mapping
  • ui (incl. scenariosUI) — WebAwesome components, HTMX wiring, Vite build
  • scenarios — at least one component scenario for a non-trivial UI piece (e.g. expense form)
  • autocomplete — category/tag/payer autocomplete
  • message catalogue (i18n) — all user-visible strings, plus number/date/currency formatting
  • authorization (ReBAC) — Authorization.require / check / filterAllowed for the permission model above
  • e2e-testing — Playwright scenarios for the golden paths

Out of initial scope (call out explicitly so we don't drift):

  • sqldb / sqldb-postgresql / sqldb-mysqlin-memory persistence only for now; sqldb integration is a planned follow-up.
  • mongo, akka-persistence, paygate, hashicorp, email, files, project-management, service-specs — out of scope for v1.

Identity provider

Use navikt/mock-oauth2-server as the OIDC provider for dev and e2e. Single small container, supports OIDC discovery, issues signed JWTs for any user, no DB, scriptable from tests.

  • dev: docker compose up brings the mock IdP alongside the app
  • e2e: tests boot the mock IdP and the app, then drive login flows
  • document Keycloak as a "production-realistic alternative" but do not require it to run the example

Pac4j is configured with the OIDC client; the example documents the standard configuration we want downstream projects to copy.

Deliverable structure (proposed)

New top-level module group under examples/:

examples/
  expense-log/
    domain/         # Group, Expense, Membership, ports
    application/    # use cases, authorization rules
    infrastructure/ # in-memory repositories, OIDC config
    server/         # Tapir endpoints + http server bootstrap
    ui/             # ScalaJS + Vite + HTMX + WebAwesome
    scenarios/      # component scenarios (if separate from main scenarios module)
    e2e/            # Playwright tests using e2e-testing
    docker/         # docker compose for mock-oauth2-server
    README.md       # how to run, what to look at

Mill build wiring follows the pattern of existing modules. The example is not published.

Acceptance criteria

Startup & security

  • App starts via a single Mill task, plus docker compose up for the mock IdP.
  • Unauthenticated requests to protected routes redirect to OIDC login.
  • After login, a session is established and propagated to ZIO layers as the current user.
  • Logout works and clears the session.
  • At least one public endpoint (e.g. health check) and one protected endpoint demonstrate the security boundary.

Authorization

  • Permission rules match the spec above and are enforced via Authorization.require / check / filterAllowed.
  • Listing endpoints use filterAllowed so non-members don't see other groups' data.
  • At least one negative test confirms a non-admin cannot edit someone else's expense.

Internationalization

  • All user-visible strings flow through the message catalogue.
  • Czech and English are both complete; switching language is a user-level setting.
  • Dates, decimal numbers, and currency all render locale-correctly (cs vs en differ; this must be visibly correct).
  • Pluralization is exercised at least once (e.g. "1 expense" / "2 expenses" / Czech variants).

HTMX / Vite / WebAwesome

  • At least one HTMX-driven flow without full page reload (filter or inline edit).
  • Vite dev server integrates with the Mill workflow per HTTP_SERVER_GUIDE.md.
  • Production build produces hashed assets served by the app.
  • WebAwesome components are used for at least: form inputs, dialog/modal, table, navigation.

Forms & codecs

  • Expense form uses forms with server-side validation and field-level error rendering.
  • Submitted data round-trips through codecs JSON.

Scenarios

  • At least one component scenario in scenarios exercising the expense form (or another non-trivial component) in isolation.

Tapir / OpenAPI

  • All HTTP endpoints declared via Tapir.
  • OpenAPI documentation is served and accurate.

e2e tests

  • e2e suite covers: login, create expense, edit own expense, admin edits another user's expense, non-member denied, language switch changes formatting.
  • e2e runs against the real app + real mock IdP, no mocks of internals.

Documentation

  • examples/expense-log/README.md explains how to run dev, run tests, and what each piece demonstrates.
  • Each module-coverage bullet above gets a short paragraph in the README pointing at the relevant code.
  • Existing guides (HTTP_SERVER_GUIDE.md, AUTHORIZATION_GUIDE.md, message catalogue docs) gain a "see the expense-log example" cross-reference where relevant.

Suggested implementation order (follow-up issues)

  1. Skeleton module + Mill build wiring + minimal Hello endpoint.
  2. Mock OIDC + Pac4j + session → first protected endpoint.
  3. Domain model (Group, Expense, Membership) + in-memory repositories.
  4. Authorization rules.
  5. Tapir endpoints + JSON codecs.
  6. UI shell with Vite + WebAwesome + i18n scaffolding.
  7. Expense list view + filters (HTMX).
  8. Expense form (create / inline edit) + forms + scenarios.
  9. Per-month summary view.
  10. Language switch + full cs/en translation pass.
  11. e2e suite.
  12. README + doc cross-references.

Each step should land as a small PR that keeps the example runnable.

Out of scope (explicit)

  • Persistent storage (planned follow-up).
  • Real-time updates (websockets/SSE).
  • Multi-currency conversion.
  • Mobile UI polish.
  • Production deployment artifacts.
# Reference example web app: end-to-end expense log ## Why `iw-support` exposes many ideas (security wiring, i18n + message catalogue, Tapir endpoints, HTMX/Vite/WebAwesome integration, ReBAC authorization, forms, codecs, scenarios, e2e testing) but downstream projects have to piece them together from guides and prior projects. We need **one canonical, runnable, tested reference application** that exercises these pieces together so we can: - demonstrate the intended way to use them, - catch integration regressions in CI rather than in downstream projects, - give new projects a copy-and-adapt starting point, - pressure-test our own APIs by building against them. This issue is the **umbrella spec** for that reference app. Implementation will be split into follow-up issues/PRs. ## Scope: shared expense log A small multi-user expense tracker. Chosen because it naturally exercises i18n (currency, dates, decimal/thousands separators), authorization (group membership, ownership, admin override), forms + validation (amount, date, category), and dynamic UI (filters, inline edit, pagination) without being contrived. ### Functional outline - **Groups** — a user belongs to one or more groups. Each group has members and at least one admin. - **Expenses** — recorded against a group. Fields: amount + currency, date, payer (group member), category, description (optional), tags (optional). - **Permissions** - any group member can list expenses in groups they belong to, - members can create expenses and edit/delete **their own**, - group admins can edit/delete **any** expense in the group, - non-members see nothing. - **Views** - login (OIDC redirect), - groups overview, - group detail with filterable expense table (month, category, payer), - inline new-expense form / inline edit, - per-month summary (total per category, per payer), - settings page (user profile, language switch). - **Languages** — Czech and English. All UI strings, dates, numbers, and currency formatted via the message catalogue. ## Module coverage (what we're exercising) The example must end up using each of the following, with a short note in the docs about *why* each is wired in: - [ ] `core` — base types, error handling, ZIO layers - [ ] `codecs` — JSON codecs for the API surface - [ ] `entity` — domain entities (Group, Expense, Membership) - [ ] `forms` — expense form, validation, error rendering - [ ] `tapir` (jvm + shared) — endpoint definitions, OpenAPI - [ ] `server/http` — HTTP server bootstrap, routes, error mapping - [ ] `ui` (incl. `scenariosUI`) — WebAwesome components, HTMX wiring, Vite build - [ ] `scenarios` — at least one component scenario for a non-trivial UI piece (e.g. expense form) - [ ] `autocomplete` — category/tag/payer autocomplete - [ ] message catalogue (i18n) — all user-visible strings, plus number/date/currency formatting - [ ] authorization (ReBAC) — `Authorization.require` / `check` / `filterAllowed` for the permission model above - [ ] `e2e-testing` — Playwright scenarios for the golden paths Out of initial scope (call out explicitly so we don't drift): - `sqldb` / `sqldb-postgresql` / `sqldb-mysql` — **in-memory persistence only for now**; sqldb integration is a planned follow-up. - `mongo`, `akka-persistence`, `paygate`, `hashicorp`, `email`, `files`, `project-management`, `service-specs` — out of scope for v1. ## Identity provider Use [`navikt/mock-oauth2-server`](https://github.com/navikt/mock-oauth2-server) as the OIDC provider for dev and e2e. Single small container, supports OIDC discovery, issues signed JWTs for any user, no DB, scriptable from tests. - dev: `docker compose up` brings the mock IdP alongside the app - e2e: tests boot the mock IdP and the app, then drive login flows - document Keycloak as a "production-realistic alternative" but do **not** require it to run the example Pac4j is configured with the OIDC client; the example documents the standard configuration we want downstream projects to copy. ## Deliverable structure (proposed) New top-level module group under `examples/`: ``` examples/ expense-log/ domain/ # Group, Expense, Membership, ports application/ # use cases, authorization rules infrastructure/ # in-memory repositories, OIDC config server/ # Tapir endpoints + http server bootstrap ui/ # ScalaJS + Vite + HTMX + WebAwesome scenarios/ # component scenarios (if separate from main scenarios module) e2e/ # Playwright tests using e2e-testing docker/ # docker compose for mock-oauth2-server README.md # how to run, what to look at ``` Mill build wiring follows the pattern of existing modules. The example is **not published**. ## Acceptance criteria ### Startup & security - [ ] App starts via a single Mill task, plus `docker compose up` for the mock IdP. - [ ] Unauthenticated requests to protected routes redirect to OIDC login. - [ ] After login, a session is established and propagated to ZIO layers as the current user. - [ ] Logout works and clears the session. - [ ] At least one **public** endpoint (e.g. health check) and one **protected** endpoint demonstrate the security boundary. ### Authorization - [ ] Permission rules match the spec above and are enforced via `Authorization.require` / `check` / `filterAllowed`. - [ ] Listing endpoints use `filterAllowed` so non-members don't see other groups' data. - [ ] At least one negative test confirms a non-admin cannot edit someone else's expense. ### Internationalization - [ ] All user-visible strings flow through the message catalogue. - [ ] Czech and English are both complete; switching language is a user-level setting. - [ ] Dates, decimal numbers, and currency all render locale-correctly (cs vs en differ; this must be visibly correct). - [ ] Pluralization is exercised at least once (e.g. "1 expense" / "2 expenses" / Czech variants). ### HTMX / Vite / WebAwesome - [ ] At least one HTMX-driven flow without full page reload (filter or inline edit). - [ ] Vite dev server integrates with the Mill workflow per `HTTP_SERVER_GUIDE.md`. - [ ] Production build produces hashed assets served by the app. - [ ] WebAwesome components are used for at least: form inputs, dialog/modal, table, navigation. ### Forms & codecs - [ ] Expense form uses `forms` with server-side validation and field-level error rendering. - [ ] Submitted data round-trips through `codecs` JSON. ### Scenarios - [ ] At least one component scenario in `scenarios` exercising the expense form (or another non-trivial component) in isolation. ### Tapir / OpenAPI - [ ] All HTTP endpoints declared via Tapir. - [ ] OpenAPI documentation is served and accurate. ### e2e tests - [ ] e2e suite covers: login, create expense, edit own expense, admin edits another user's expense, non-member denied, language switch changes formatting. - [ ] e2e runs against the real app + real mock IdP, no mocks of internals. ### Documentation - [ ] `examples/expense-log/README.md` explains how to run dev, run tests, and what each piece demonstrates. - [ ] Each module-coverage bullet above gets a short paragraph in the README pointing at the relevant code. - [ ] Existing guides (`HTTP_SERVER_GUIDE.md`, `AUTHORIZATION_GUIDE.md`, message catalogue docs) gain a "see the expense-log example" cross-reference where relevant. ## Suggested implementation order (follow-up issues) 1. Skeleton module + Mill build wiring + minimal `Hello` endpoint. 2. Mock OIDC + Pac4j + session → first protected endpoint. 3. Domain model (Group, Expense, Membership) + in-memory repositories. 4. Authorization rules. 5. Tapir endpoints + JSON codecs. 6. UI shell with Vite + WebAwesome + i18n scaffolding. 7. Expense list view + filters (HTMX). 8. Expense form (create / inline edit) + forms + scenarios. 9. Per-month summary view. 10. Language switch + full cs/en translation pass. 11. e2e suite. 12. README + doc cross-references. Each step should land as a small PR that keeps the example runnable. ## Out of scope (explicit) - Persistent storage (planned follow-up). - Real-time updates (websockets/SSE). - Multi-currency conversion. - Mobile UI polish. - Production deployment artifacts.
Sign in to join this conversation.
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
iterative-works/support#22
No description provided.