# Buzz Sheet Buzz Sheet is a Thai-first guide CMS for turning structured character, build, team, rotation, and comparison data into responsive game-guide pages. Production target: `https://guide.sudloh.com` This project is an independent community tool for Genshin Impact players. It is not affiliated with or endorsed by HoYoverse. ## Included capabilities - Searchable character guide directory. - Structured editors for overview, weapons, artifacts, constellations, teams, and custom sections with autosave and conflict recovery. - Better Auth email/password login with administrator-managed accounts. - Private S3-compatible media uploads with reference-aware same-origin delivery. - PostgreSQL transactions and a retryable outbox. - Shared Redis cache invalidation and privacy-safe Server-Sent Events across replicas. - Discord-triggered Lunaris catalog updates with exact-version synchronization. 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` | Administrator email/password sign-in | | `/admin/register` | Create and remove administrator accounts | | `/admin` | Character and page overview | | `/admin/[character]/[page]` | Visual page editor | | `/admin/create` | Create a structured guide from the synced character catalog | | `/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 Install dependencies and create your local environment file: ```bash bun install --frozen-lockfile cp .env.example .env ``` `.env.example` contains placeholders only. Configure `.env` yourself; it is ignored by Git and must never be committed. `BETTER_AUTH_URL` must exactly match the application origin. Set `BASE_URL` to the public site origin for SEO metadata, sitemap, and robots.txt. Existing administrators can create additional credential accounts from `/admin/register`; public registration is disabled. Prepare the database and start the application: ```bash bun run db:migrate bun run dev ``` ### Fixture-only demo The UI can be inspected without PostgreSQL, Redis, S3, or authentication credentials by running: ```bash BUZZ_DEMO_MODE=true bun run dev ``` 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 import:guides` | Preview the one-time Google Sheets guide migration (`-- --apply` to write) | | `bun run worker:discord` | Listen for Lunaris update announcements | | `bun run worker:outbox` | Process cache and event outbox records | ## Runtime design Accepted edits use an `expectedVersion` and commit guide content, media reference counts, 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 truth. The Discord catalog worker watches the private channel configured by `DISCORD_CHANNEL_ID`. It parses new Lunaris `Version Change` announcements and then fetches and synchronizes the latest version reported by Lunaris. Startup only connects the listener; it does not replay channel history or start a sync. Worker and sync results are sent to `DISCORD_LOG_CHANNEL_ID`. Enable the Discord Message Content intent and grant the bot View Channel and Send Messages in the configured channels. Public guides 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 published guide references it; otherwise administrator authorization is required. ## Verification Run the complete host-safe verification set with: ```bash bun install --frozen-lockfile bun run test bun run typecheck 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 workers on Bun as the unprivileged numeric UID/GID `1000:1000`. The migration image runs the committed Drizzle migrations with the same identity. ```bash docker build \ --target app \ --build-arg BASE_URL=https://guide.sudloh.com \ --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. `BASE_URL` is required during the Next.js build and at runtime. CI supplies the build argument, and the Kubernetes ConfigMap supplies the runtime value. Keep both values aligned when changing the public site origin. ## Kubernetes Kustomize resources live in `k8s/` and define: - Namespace `buzz-sheet`. - Two rolling web replicas, one outbox worker, and one Discord catalog worker. - ClusterIP port 3000 and Traefik ingress for `guide.sudloh.com`. - Cloudflare edge TLS with the cluster's standard HTTP Traefik origin route. - Startup, liveness, and dependency-aware readiness probes. - Web requests of 500m CPU/1 GiB and limits of 1 CPU/2 GiB. - HPA from 2 to 6 web replicas at 70% CPU or 75% memory and a PDB with one available. - Non-root, read-only containers with dropped capabilities and seccomp. - ARM64 scheduling constraints matching the production hosts and images. - 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 a `buzz-sheet-env` Secret in the `buzz-sheet` namespace containing: ```text DATABASE_URL BETTER_AUTH_SECRET REDIS_URL CATALOG_SYNC_CONCURRENCY S3_ENDPOINT S3_PUBLIC_URL 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. Every page also subscribes to `/api/active`. When a serving pod reports a newer deployment ID than the browser's current build, a persistent Thai notification offers a full reload into the new release. 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. After the repository variable `DEPLOY_ENABLED` is explicitly set to `true`, 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 | | --- | --- | | `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 both worker deployments. Schema changes must follow an expand/contract sequence so the previous application revision stays compatible during a rolling update. ## Acknowledgements This project would not be possible without the open-source software and community services it builds on: - [Lunaris](https://lunaris.moe/) — character, talent, material, and game data used by catalog synchronization and guide tools. Buzz Sheet links back to Lunaris for material references where appropriate. - [Next.js](https://nextjs.org/) — application framework. - [React](https://react.dev/) — user interface library. - [Bun](https://bun.sh/) — JavaScript runtime and package manager. - [PostgreSQL](https://www.postgresql.org/) — primary database. - [Redis](https://redis.io/) — cache, event transport, and presence state. - [Discord.js](https://discord.js.org/) — Discord catalog update worker. - [Drizzle ORM](https://orm.drizzle.team/) — database schema and migrations. - [shadcn/ui](https://ui.shadcn.com/), [Tailwind CSS](https://tailwindcss.com/), [Lucide](https://lucide.dev/), and [dnd-kit](https://dndkit.com/) — interface components, styling, icons, and drag-and-drop interactions. Special thanks are also available on the site’s [Special Thanks page](https://guide.sudloh.com/thanks). Genshin Impact and its characters, names, and assets are property of their respective rights holders. Please review the applicable terms and licenses for each third-party dependency before redistributing or deploying this project.