From efb61521ca5c68fe96b6175937ab63fd98a8210a Mon Sep 17 00:00:00 2001 From: gunshiz Date: Sat, 29 Aug 2026 06:12:13 +0000 Subject: [PATCH] docs: document setup verification and deployment --- README.md | 267 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 246 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index e215bc4..fe06ee3 100644 --- a/README.md +++ b/README.md @@ -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= \ + --build-arg VCS_REF= \ + --tag buzz-sheet: \ + . + +docker build \ + --target migration \ + --build-arg VCS_REF= \ + --tag buzz-sheet:migrate- \ + . +``` + +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: +registry.neko-piranha.ts.net/astral/buzz-sheet:migrate- +``` + +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.