131 lines
5.8 KiB
Markdown
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 |
|