docs: document setup verification and deployment
This commit is contained in:
@@ -1,36 +1,261 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
# Buzz Sheet
|
||||
|
||||
## Getting Started
|
||||
Buzz Sheet is a Thai-first guide CMS for turning structured character, build,
|
||||
team, rotation, and comparison data into responsive game-guide pages. Authors
|
||||
work in a visual block editor; visitors get readable guides rather than a
|
||||
spreadsheet interface.
|
||||
|
||||
First, run the development server:
|
||||
Production target: `https://sheet.sudloh.com`
|
||||
|
||||
## Included capabilities
|
||||
|
||||
- Searchable character directory with element and role filters.
|
||||
- Unlimited character pages with custom slugs, ordering, notes, and visibility.
|
||||
- Visual admin workspace with inline editing, inspector, preview, keyboard and
|
||||
pointer reordering, 750 ms autosave, conflict recovery, and revisions.
|
||||
- Six reusable templates covering classic guides, rotations, one-page builds,
|
||||
multi-role builds, team databases, and calculation-backed comparisons.
|
||||
- Validated block schemas with migrations and a safe fallback for unknown
|
||||
future block types.
|
||||
- Restricted decimal formula engine with named references, dependency ordering,
|
||||
cycle detection, typed failures, and presentation-only rounding.
|
||||
- Read-only comparison charts with visible values and accessible table fallbacks.
|
||||
- Google authentication restricted to one verified `ADMIN_EMAIL`.
|
||||
- Private S3-compatible media uploads with reference-aware same-origin delivery.
|
||||
- PostgreSQL transactions, immutable data-source versions, revisions, and a
|
||||
retryable outbox.
|
||||
- Shared Redis cache invalidation and privacy-safe Server-Sent Events across
|
||||
replicas.
|
||||
|
||||
The five reference workbooks are represented only by sanitized structural and
|
||||
formula fixtures. Their guide text and media are not imported or published.
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | Purpose |
|
||||
| --- | --- |
|
||||
| `/` | Searchable public character directory |
|
||||
| `/[character]` | Redirect to the first visible page |
|
||||
| `/[character]/[page]` | Render a public guide page |
|
||||
| `/admin/login` | Google administrator sign-in |
|
||||
| `/admin` | Character and page overview |
|
||||
| `/admin/[character]/[page]` | Visual page editor |
|
||||
| `/admin/templates` | Apply reusable guide templates |
|
||||
| `/media/[id]` | Authorized same-origin media response |
|
||||
| `/api/health` | Liveness response |
|
||||
| `/api/health?ready=1` | PostgreSQL and Redis readiness response |
|
||||
|
||||
A page is public only when both its page and character are visible. A visible
|
||||
character without a visible page returns 404.
|
||||
|
||||
## Local development
|
||||
|
||||
Requirements:
|
||||
|
||||
- Bun 1.3.14
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
- Private S3-compatible object storage
|
||||
- Google OAuth credentials
|
||||
|
||||
Install dependencies and create your local environment file:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
bun install --frozen-lockfile
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
`.env.example` contains placeholders only. Configure `.env` yourself; it is
|
||||
ignored by Git and must never be committed. For Google OAuth, register this
|
||||
authorized redirect URI:
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
```text
|
||||
http://localhost:3000/api/auth/callback/google
|
||||
```
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
Use the production origin in place of `http://localhost:3000` for the deployed
|
||||
OAuth client. `BETTER_AUTH_URL` must exactly match that origin, and access is
|
||||
granted only when Google reports a verified email equal to `ADMIN_EMAIL`.
|
||||
|
||||
## Learn More
|
||||
Prepare the database and start the application:
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
```bash
|
||||
bun run db:migrate
|
||||
bun run db:seed
|
||||
bun run dev
|
||||
```
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
The seed command installs the six starter built-in templates; it does not publish
|
||||
reference workbook content.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
### Fixture-only demo
|
||||
|
||||
## Deploy on Vercel
|
||||
The UI can be inspected without PostgreSQL, Redis, S3, or Google by running:
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
```bash
|
||||
BUZZ_DEMO_MODE=true bun run dev
|
||||
```
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
Demo mode uses sanitized local fixtures and bypasses production authorization
|
||||
and external services. Never enable it in a deployed environment.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Purpose |
|
||||
| --- | --- |
|
||||
| `bun run dev` | Start the Next.js development server |
|
||||
| `bun run test` | Run the Vitest unit and integration suite |
|
||||
| `bun run test:coverage` | Produce V8 coverage output |
|
||||
| `bun run typecheck` | Run TypeScript without emitting files |
|
||||
| `bun run lint` | Run ESLint |
|
||||
| `bun run build` | Compile the production application |
|
||||
| `bun run db:generate` | Generate a Drizzle migration from schema changes |
|
||||
| `bun run db:migrate` | Apply committed Drizzle migrations |
|
||||
| `bun run db:seed` | Upsert the built-in templates |
|
||||
| `bun run worker:outbox` | Process cache and event outbox records |
|
||||
|
||||
## Runtime design
|
||||
|
||||
Accepted edits use an `expectedVersion` and commit content, immutable data-source
|
||||
versions, revision snapshots, media references, and outbox records in one
|
||||
PostgreSQL transaction. The outbox worker then invalidates affected Redis tags
|
||||
before publishing an opaque `{ type, id, version }` event. Connected clients
|
||||
refetch authoritative state; SSE never contains page content and is not the
|
||||
source of correctness.
|
||||
|
||||
Public snapshots use a shared Redis-backed Next.js cache. Admin routes remain
|
||||
dynamic. SSE streams send a 90-second heartbeat and close after 30 minutes so
|
||||
clients reconnect. Traefik buffering is disabled by the response headers.
|
||||
|
||||
Media objects remain private in S3. `/media/[id]` serves an object publicly only
|
||||
while a visible current snapshot references it; otherwise administrator
|
||||
authorization is required. Retained revisions keep their referenced objects
|
||||
alive.
|
||||
|
||||
Formula fields accept arithmetic only: `+`, `-`, `*`, `/`, parentheses,
|
||||
percentages, unary signs, and stable field references. They cannot execute HTML,
|
||||
JavaScript, React, CSS, external scripts, packages, or network calls.
|
||||
|
||||
## Verification
|
||||
|
||||
Run the complete host-safe verification set with:
|
||||
|
||||
```bash
|
||||
bun install --frozen-lockfile
|
||||
bun run test
|
||||
bunx tsc --noEmit
|
||||
bun run lint
|
||||
kubectl kustomize k8s/ >/dev/null
|
||||
```
|
||||
|
||||
The formula suite validates the exact sanitized corpus of 379 extracted formula
|
||||
cells plus arithmetic edge cases. An optional Redis integration test uses
|
||||
independent connections for two conceptual replicas:
|
||||
|
||||
```bash
|
||||
REDIS_INTEGRATION_URL=redis://127.0.0.1:6379 \
|
||||
bun run test -- tests/redis-replication.integration.test.ts
|
||||
```
|
||||
|
||||
It uses unique key and channel prefixes and removes its cache state afterward.
|
||||
Without `REDIS_INTEGRATION_URL`, that external-service test is skipped.
|
||||
|
||||
## Containers
|
||||
|
||||
The application image compiles Next.js in a Node builder stage, then runs the
|
||||
standalone server and bundled outbox worker on Bun as the unprivileged `bun`
|
||||
user. The migration image runs the committed Drizzle migrations on Bun.
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--target app \
|
||||
--build-arg NEXT_DEPLOYMENT_ID=<immutable-revision> \
|
||||
--build-arg VCS_REF=<immutable-revision> \
|
||||
--tag buzz-sheet:<immutable-revision> \
|
||||
.
|
||||
|
||||
docker build \
|
||||
--target migration \
|
||||
--build-arg VCS_REF=<immutable-revision> \
|
||||
--tag buzz-sheet:migrate-<immutable-revision> \
|
||||
.
|
||||
```
|
||||
|
||||
The runtime images do not contain local `.env` files.
|
||||
|
||||
## Kubernetes
|
||||
|
||||
Kustomize resources live in `k8s/` and define:
|
||||
|
||||
- Namespace `buzz-sheet`.
|
||||
- Two rolling web replicas and one outbox worker.
|
||||
- ClusterIP port 3000 and Traefik ingress for `sheet.sudloh.com`.
|
||||
- Externally provisioned `sheet-sudloh-com-tls`.
|
||||
- Startup, liveness, and dependency-aware readiness probes.
|
||||
- Web requests of 500m CPU/512 MiB and limits of 1 CPU/1 GiB.
|
||||
- HPA from 2 to 6 web replicas at 70% CPU and a PDB with one available.
|
||||
- Non-root, read-only containers with dropped capabilities and seccomp.
|
||||
- A versioned migration Job and namespace-scoped CI deployer permissions.
|
||||
|
||||
PostgreSQL, Redis, and S3 are external services. The manifests deliberately do
|
||||
not create in-cluster data stores or credentials.
|
||||
|
||||
### External prerequisites
|
||||
|
||||
Before the first rollout, a cluster administrator must provision the TLS secret
|
||||
and a `buzz-sheet-env` Secret in the `buzz-sheet` namespace containing:
|
||||
|
||||
```text
|
||||
DATABASE_URL
|
||||
BETTER_AUTH_SECRET
|
||||
GOOGLE_CLIENT_ID
|
||||
GOOGLE_CLIENT_SECRET
|
||||
ADMIN_EMAIL
|
||||
REDIS_URL
|
||||
S3_ENDPOINT
|
||||
S3_BUCKET
|
||||
S3_ACCESS_KEY_ID
|
||||
S3_SECRET_ACCESS_KEY
|
||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
|
||||
```
|
||||
|
||||
`NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` must be generated once and remain stable
|
||||
across replicas and rolling deployments. Do not place secret values in the
|
||||
ConfigMap or commit them to this repository.
|
||||
|
||||
The base manifests contain deliberate `replace-me` image and deployment values.
|
||||
For initial bootstrap, the platform operator applies `k8s/base` through an
|
||||
environment overlay that supplies an existing immutable image revision. The
|
||||
aggregate `k8s/` kustomization includes the migration resource for rendering and
|
||||
validation; normal releases create their own revision-named migration Job.
|
||||
|
||||
Use this command for local manifest validation only:
|
||||
|
||||
```bash
|
||||
kubectl kustomize k8s/ >/dev/null
|
||||
```
|
||||
|
||||
### Gitea Actions release
|
||||
|
||||
`.gitea/workflows/ci.yml` verifies every push and pull request. A successful
|
||||
push to `main` builds multi-architecture images at:
|
||||
|
||||
```text
|
||||
registry.neko-piranha.ts.net/astral/buzz-sheet:<git-sha>
|
||||
registry.neko-piranha.ts.net/astral/buzz-sheet:migrate-<git-sha>
|
||||
```
|
||||
|
||||
Configure these Gitea secrets later:
|
||||
|
||||
| Secret | Purpose |
|
||||
| --- | --- |
|
||||
| `REGISTRY_USERNAME` | Container registry login |
|
||||
| `REGISTRY_PASSWORD` | Container registry login |
|
||||
| `KUBE_CONFIG_B64` | Base64 kubeconfig for the `ci-deployer` identity |
|
||||
| `REDIS_INTEGRATION_URL` | Optional isolated Redis endpoint for CI integration coverage |
|
||||
|
||||
The deploy job creates a revision-named migration Job, waits for completion,
|
||||
updates `NEXT_DEPLOYMENT_ID`, and only then rolls out the immutable application
|
||||
image to the web and worker deployments. Schema changes must follow an
|
||||
expand/contract sequence so the previous application revision stays compatible
|
||||
during a rolling update.
|
||||
|
||||
Reference in New Issue
Block a user