Files
suaveplan/apps/demo/README.md
2026-05-27 23:29:53 +01:00

131 lines
5.8 KiB
Markdown

# @suaveplan/demo
Unified Bun + Hono server with a React 19 SPA front-end and a JSON API
in one process. Demonstrates locale-prefix routing across five base
locales, `@suaveplan/api-plugin-auth`-driven Better-Auth authentication
backed by SurrealDB, and React + Tailwind v4 styling.
## What's wired up
- **Server** — single Bun process running a Hono app on port `3300`
(override with `PORT=…`). The Hono instance is the one owned by a
`SuaveApp` from `@suaveplan/api`. `/api/health` is registered by
`registerHealthRoutes`; `/api/auth/*` is mounted by
`@suaveplan/api-plugin-auth` (9 OpenAPI routes + the Better-Auth
catch-all); `/api/me` is a demo convenience that calls
`authService.getSession(headers)`; everything else falls through to
the Vite-built client bundle in `dist/client/`, with `index.html`
fallback so the client router owns deep links.
- **Auth** — the canonical genesis pipeline. The Hono app is assembled
via `defineApp()` from `@suaveplan/api`, with `createAuthPlugin(...)`
from `@suaveplan/api-plugin-auth` registered as a plugin. The auth
plugin's 6-step `onInit` applies the bundled 17-table Better-Auth
SurrealQL schema, constructs the real Better-Auth instance backed by
`betterAuthSurrealAdapter` (from `@suaveplan/better-auth-surrealdb`,
the Better-Auth v1.6 `DBAdapterInstance` factory), registers the
typed `authService` on the host `ServiceRegistry`, mounts the 9
OpenAPI routes plus the Better-Auth catch-all under `/api/auth/*`,
and installs the global session-resolver middleware so every request
gets a typed `Principal` on its Hono context. A small inline
`demo-db-seed` plugin (in `src/server/auth.ts`) drops the live
`SurrealAdapter` into `ServiceRegistry["db"]` during `onRegister` so
the auth plugin can resolve it during `onInit`. Backing store is an
**external SurrealDB** reached via `SURREAL_URL` (default
`ws://localhost:8000`), namespace `SURREAL_NS` (default `demo`),
database `SURREAL_DB` (default `demo`), credentials `SURREAL_USER` /
`SURREAL_PASS` (default `root` / `root`).
- **Client** — React 19 + Vite + `@tailwindcss/vite` for Tailwind v4.
Routing via `react-router-dom@7` with a locale-prefix design. The
app root composes `<ReactUiProvider>` (from `@suaveplan/react-ui`)
around the entire tree, seeded with a singleton `I18nInstance`
(`createI18n` from `@suaveplan/i18n`) and the chassis `lightTheme`
plugin. `<ReactUiProvider>` internally wraps
`<TranslationProvider>` → `<ThemeProvider>` → `<PackRegistryProvider>`
and auto-registers the matching chassis locale bundle on mount /
language change, so any react-ui primitive that calls
`useTranslation(...)` finds a populated catalog. Form pages
(`/auth/login`, `/auth/register`, `/auth/forgot-password`,
`/user/settings`) compose `<Card>`, `<Field>`, `<FieldLabel>`,
`<Input>`, `<Button>`, `<Alert>` from the chassis — no hand-rolled
Tailwind on those primitives. Demo-specific copy continues to flow
through a typed `LocaleProvider` so the page-level message catalogs
stay co-located and statically checked; the i18n instance is driven
to the matching BCP47 variant (`en` → `en-GB`, `zh` → `zh-CN`,
`ar` → `ar-AE`, `cy` → `cy-GB`, `ru` → `ru-RU`) via
`syncDemoLocale(base)` from a `useEffect`.
## Locale-prefix routing
Five base locales: `en`, `zh`, `ar`, `cy`, `ru`. Region variants
(e.g. `en-GB`, `zh-CN`, `ar-EG`) parse case-insensitively and
canonicalise to lowercase-base + UPPERCASE-region. Anything else
renders the `NotFoundPage` with a language picker.
- `/` → `Navigate` to `/<detected>/home` (uses `navigator.languages`).
- `/<locale>/home`
- `/<locale>/about-us`
- `/<locale>/contact-us`
- `/<locale>/privacy-policy`
- `/<locale>/terms-of-service`
- `/<locale>/auth/login`
- `/<locale>/auth/register`
- `/<locale>/auth/forgot-password`
- `/<locale>/auth/logout`
- `/<locale>/user/control-panel` *(gated — `RequireAuth`)*
- `/<locale>/user/settings` *(gated — `RequireAuth`)*
- `/<locale>/admin/control-panel` *(gated — `RequireRole admin`)*
- `/<locale>/admin/settings` *(gated — `RequireRole admin`)*
`ar` flips `<html dir="rtl">`. The `<LocaleSwitcher>` rewrites the URL
in place when a user changes language.
## Running the demo
The demo requires a reachable SurrealDB instance. The simplest path:
```bash
docker run -d --name demo-surreal -p 8000:8000 \
surrealdb/surrealdb:latest start --user root --pass root --bind 0.0.0.0:8000
```
Then, from the repo root:
```bash
bun install # workspace deps
bunx turbo build --filter=@suaveplan/demo # build api/auth deps + dist/client
bun run --filter=@suaveplan/demo dev # boot the server on :3300
```
Visit `http://localhost:3300` — it should redirect to `/en/home` (or
your browser's preferred locale if it matches one of the five).
Smoke endpoints:
```bash
curl http://localhost:3300/api/health
curl http://localhost:3300/api/me
curl http://localhost:3300/api/auth/get-session
```
End-to-end sign-up roundtrip — produces a 200 with a Better-Auth
session token + user record:
```bash
curl -X POST http://localhost:3300/api/auth/sign-up/email \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","password":"hunter2","name":"Alice"}'
```
Env overrides (all optional):
| Variable | Default |
| ---------------------- | ------------------------- |
| `PORT` | `3300` |
| `SURREAL_URL` | `ws://localhost:8000` |
| `SURREAL_NS` | `demo` |
| `SURREAL_DB` | `demo` |
| `SURREAL_USER` | `root` |
| `SURREAL_PASS` | `root` |
| `BETTER_AUTH_URL` | `http://localhost:3300` |
| `BETTER_AUTH_SECRET` | demo placeholder — set in prod |