feat(seed): seed demo data on a new account's first visit (PMS-157) #146

Merged
nrupard merged 2 commits from feat/pms-157-new-account-seeding into main 2026-06-09 22:08:55 +02:00
Owner

What

Seeds a small set of illustrative rows into a brand-new account on its first authenticated visit (PMS-157), so a fresh account demonstrates how Mokosh works instead of showing an empty shell. Demo set: one company, two contacts, three tickets, inserted through the real ContactService / TicketService create paths (validation, FK checks, and audit rows all apply).

How it triggers

New seed module with a middleware (seed_middleware) added inner of the auth middleware. Auth populates AuthState; this layer then reads the resolved tenant/user and spawns SeedService::ensure_demo_seeded detached, so seeding never adds latency to the triggering request and any failure is logged and swallowed (it can never break a request).

Lazy first-visit was chosen over hooking TenantService::create_tenant because Bunyip users JIT-land in a pre-existing tenant and never pass through tenant creation.

Idempotency (three guards)

  • In-process seen-set: short-circuits every request after the first confirmation, so the steady state touches no database.
  • Atomic compare-and-set on tenants.settings->>'demo_seeded': claims the seed exactly once, even under a burst of concurrent first requests (only the request that flips the flag proceeds).
  • Emptiness check: skips tenants that already have companies, so an established tenant predating this feature is never polluted on the first visit after a deploy (its flag is still set so the check stops running).

Kill-switch

Set MOKOSH_DEMO_SEED=false to disable, e.g. for E2E/staging that run against the shared default tenant.

Tests

tests/seed_demo.rs covers the happy path (one company, two contacts, three tickets, flag set), cross-process idempotency (a fresh service does not re-seed), and the emptiness guard (a tenant with existing data gets the flag but no demo rows). cargo check --all-targets, clippy --all-targets, and fmt --check are clean; integration tests run in CI.

#PMS-157

🤖 Generated with Claude Code

## What Seeds a small set of illustrative rows into a brand-new account on its first authenticated visit (PMS-157), so a fresh account demonstrates how Mokosh works instead of showing an empty shell. Demo set: one company, two contacts, three tickets, inserted through the real `ContactService` / `TicketService` create paths (validation, FK checks, and audit rows all apply). ## How it triggers New `seed` module with a middleware (`seed_middleware`) added *inner* of the auth middleware. Auth populates `AuthState`; this layer then reads the resolved tenant/user and spawns `SeedService::ensure_demo_seeded` detached, so seeding never adds latency to the triggering request and any failure is logged and swallowed (it can never break a request). Lazy first-visit was chosen over hooking `TenantService::create_tenant` because Bunyip users JIT-land in a pre-existing tenant and never pass through tenant creation. ## Idempotency (three guards) - In-process seen-set: short-circuits every request after the first confirmation, so the steady state touches no database. - Atomic compare-and-set on `tenants.settings->>'demo_seeded'`: claims the seed exactly once, even under a burst of concurrent first requests (only the request that flips the flag proceeds). - Emptiness check: skips tenants that already have companies, so an established tenant predating this feature is never polluted on the first visit after a deploy (its flag is still set so the check stops running). ## Kill-switch Set `MOKOSH_DEMO_SEED=false` to disable, e.g. for E2E/staging that run against the shared default tenant. ## Tests `tests/seed_demo.rs` covers the happy path (one company, two contacts, three tickets, flag set), cross-process idempotency (a fresh service does not re-seed), and the emptiness guard (a tenant with existing data gets the flag but no demo rows). `cargo check --all-targets`, `clippy --all-targets`, and `fmt --check` are clean; integration tests run in CI. #PMS-157 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(seed): seed demo data on a new account's first visit (PMS-157)
All checks were successful
E2E (staging) / Playwright against staging (pull_request) Successful in 58s
Check / fmt + clippy + compile + tests (pull_request) Successful in 2m17s
Build OCI container / Build and push mokosh-api image (push) Successful in 3m34s
909dc0705b
A brand-new account now gets a small set of illustrative rows on its first authenticated visit so it shows how Mokosh works instead of presenting an empty shell. The seed is one demo company, two contacts, and three tickets, inserted through the real ContactService / TicketService create paths (so validation, FK checks, and audit rows all apply).

Trigger is a middleware (`seed_middleware`) that runs inner of the auth middleware: auth populates AuthState, then this reads the resolved tenant/user and spawns `SeedService::ensure_demo_seeded` detached. Seeding therefore never adds latency to the triggering request, and any failure is logged and swallowed (it can never break a request). Lazy first-visit was chosen over hooking tenant creation because Bunyip users JIT-land in a pre-existing tenant and never pass through `TenantService::create_tenant`.

Idempotency has three guards: an in-process seen-set that short-circuits every request after the first confirmation (steady state touches no DB), an atomic compare-and-set on `tenants.settings->>'demo_seeded'` that claims the seed exactly once even under a burst of concurrent first requests, and an emptiness check that skips tenants which already have companies so an established tenant predating this feature is never polluted on the first visit after a deploy (its flag is still set so the check stops running).

Set `MOKOSH_DEMO_SEED=false` to disable, e.g. for E2E/staging that run against the shared default tenant.

Integration tests cover the happy path (one company, two contacts, three tickets, flag set), cross-process idempotency (a fresh service does not re-seed), and the emptiness guard (a tenant with existing data gets the flag but no demo rows).

#PMS-157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(seed): make demo ticket seeding best-effort; document E2E kill-switch
All checks were successful
E2E (staging) / Playwright against staging (pull_request) Successful in 1m8s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + compile + tests (pull_request) Successful in 3m27s
Build OCI container / Build and push mokosh-api image (push) Successful in 7m28s
b6f6ba1067
Ticket creation in the demo seed is now per-row best-effort instead of error-propagating. `create_ticket` requires the tenant's default status/priority/queue to exist; a tenant lacking them would error and, with the previous `?`, abort the whole seed after the company + contacts had already committed, leaving a half-seeded account that the idempotency flag then prevented from ever completing. A failing ticket is now logged and skipped so the core demo (company + contacts) always lands.

Also documents in e2e/README.md that the staging/E2E deployment must set `MOKOSH_DEMO_SEED=false`, since demo rows are not `e2e-`-tagged and would otherwise survive teardown and skew list assertions against the shared E2E tenant. (The emptiness guard already protects a non-empty tenant, but the flag should be set explicitly.)

#PMS-157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/pms-157-new-account-seeding 2026-06-09 22:08:56 +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/mokosh-server!146
No description provided.