gunshiz 45c1567dff
CI / Verify (push) Successful in 1m4s
CI / Build immutable images and deploy (push) Successful in 2m1s
fix : not fix image resolition
45c1567dff · 2026-09-04 16:30:55 +07:00
173 Commits
2026-09-04 16:30:55 +07:00
2026-09-04 16:12:43 +07:00
2026-09-03 21:46:35 +07:00
2026-09-03 23:24:51 +07:00
2026-08-28 13:48:23 +00:00
2026-08-31 22:13:11 +07:00
2026-09-03 23:24:51 +07:00
2026-08-28 15:57:12 +00:00
2026-08-29 13:45:50 +00:00

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. Authors work in a visual block editor; visitors get readable guides rather than a spreadsheet interface.

Production target: https://guide.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.
  • Better Auth email/password login with administrator-managed accounts.
  • 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.
  • 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:

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. Existing administrators can create additional credential accounts from /admin/register; public registration is disabled.

Prepare the database and start the application:

bun run db:migrate
bun run db:seed
bun run dev

The seed command installs the six starter built-in templates; it does not publish reference workbook content.

Fixture-only demo

The UI can be inspected without PostgreSQL, Redis, S3, or authentication credentials by running:

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 db:seed Upsert the built-in templates
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 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 truth.

The Discord catalog worker watches the private channel configured by DISCORD_CHANNEL_ID. It parses Lunaris Version Change announcements and synchronizes that exact version. On startup it checks recent channel history, so it does not need to poll Lunaris while idle. Enable the Discord Message Content intent and grant the bot View Channel and Read Message History.

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:

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:

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.

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, 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/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.
  • 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:

DATABASE_URL
BETTER_AUTH_SECRET
REDIS_URL
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:

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:

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
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.

S
Description
No description provided
Readme
18 MiB
Languages
TypeScript 99.2%
CSS 0.4%
JavaScript 0.3%
Dockerfile 0.1%