16 KiB
Executable File
Buzz Events
Next.js 16 application running multiple community events for YouTuber Buzz. Built with TypeScript, Bun, PostgreSQL, and Redis.
Branches
Donation
Payment-processing and stream-overlay system with two payment methods:
- TrueMoney (TMN): Voucher gift link redemption via Sastify API
- PromptPay: Slip image upload verified by SlipOK or Slip2Go, selected in admin settings
Key features:
- Real-time overlay widget (OBS-ready): SSE-driven animated popup with donor image, name, amount, message; plays SFX + Gemini TTS audio announcement (
"<name> โดเนทมา <amount> บาท") - Top donors leaderboard at
/donate/top— podium (1st–3rd) + table (4th–10th) - Admin panel at
/donate/admin— view donations, re-send popups, test popup, force-reload all widgets, copy artifact UIDs, view donor images - Heartbeat system: Widget health monitoring with auto-recovery after 6+ missed heartbeats
- Sub-10 THB donations skip overlay but update leaderboard
- Queue-skip for artifact submissions (promoted queue)
Artifact
Moderator-supervised character showcase ID review system:
- Public submission at
/artifact— UID + character + comment form - Enka Network integration: Auto-fetches showcase characters from UID
- Free queue: FIFO, 1 character reviewed per submission
- Donation skip-queue: Promoted submissions (
queue = NULL), up to 8 characters, visually distinct in admin (yellow border, bitcoin icon) - Admin panel: Sidebar with sorted submission list (special queue first), checkmark toggle, Enka card browser (iframe + rendered card), random picker, wipe/reset
- Queue management: Lock toggle, configurable capacity limit (
-1= unlimited) - Card generation: External MTS service renders character cards; cached as
byteain DB with revalidation support - Widget:
/widget/artifact-count— OBS overlay displaying count/limit
Rubgram (รับกรรมแทนทางบ้าน)
Paid endgame content service where Buzz plays on your account:
- Discord OAuth2 required — auto-joins guild, session persisted via cookie token
- Service selector: Multiple configurable service types with real-time price estimation; "all services" discount
- Free queue counter: Admin-configurable free slots (price = 0)
- 20-minute payment window with PromptPay QR code and slip upload
- SlipOK / Slip2Go verification with deduplication by
transRef - Expiration system: Unpaid submissions auto-expire with queue rebalancing; expired users can restore and pay
- Admin panel: Lock/limit/free controls, manual submission creation, Discord ping ("ถึงคิวแล้ว"), slip archive viewer by round, monthly archive
- Widget:
/widget/rubgram-count— OBS overlay displaying count/limit
Tierlist
Community character tier rankings:
- Multi-type, multi-version: Browse by category (e.g. Endgame Content) and patch
- Drag-and-drop via dnd-kit — place characters into SS/A/B/C/D tiers within On-Field DPS / Off-Field DPS / Support columns
- Real-time collaboration via SSE — all connected clients sync instantly
- Comments & badges assignable to individual placements
- Admin edit mode at
/:type/:ver/admin - Data sourced from Project Amber
Guide
Character build guide directory:
- Searchable grid with debounced ilike search
- Each card links to an external Google Sheet
- Admin hide/unhide controls
Admin
Centralized management panel at /admin (guarded by better-auth):
- Dashboard: Random Nod-Krai welcome messages, health monitor (DB, Enka, Amber, Redis/SSE)
- Character manager: CRUD for Genshin characters (element, weapon, stars, CDN image, Amber ID)
- CDN file manager: Upload, rename, import from URL
- Guide admin: Hide/unhide guides
- Settings: Enka Network toggle, manual Amber sync, account (WIP)
- Audit log: Last 1000 admin actions with user info
- Section shortcuts: Links to artifact, rubgram, donate, and tierlist admin
Architecture Diagram has been moved to DIAGRAM.md
Architecture
- Framework: Next.js 16 (App Router), Turbopack, React 19 — hybrid SSR/RSC/CSR
- Runtime: Bun (1.3.14-alpine) — development, production, and backend
- Language: TypeScript (strict, bundler module resolution)
- UI: shadcn/ui (Radix primitives), Tailwind CSS 4, motion, lucide-react, @tanstack/react-table, recharts
- Forms: react-hook-form, zod validation
- Auth: better-auth (email/password, admin role), Discord OAuth2 (Rubgram), internal Redis-backed tokens (service-to-service)
- Database: PostgreSQL via Drizzle ORM (5 schemas). All IDs are UUIDv7. Binary data stored as
bytea. - Real-time: Redis pub/sub → Server-Sent Events (SSE) — auto-reconnecting EventSource client with 90s heartbeat, 30min timeout
- Analytics & Errors: PostHog (cloud, proxied via Next.js rewrites)
- TTS: Google Gemini 2.5 Flash Preview TTS — randomized voices, WAV output, Redis-cached (7d), 1000-char limit
- Background: Canvas-based star renderer (~500 stars + shooting stars, capped at 30fps)
- Font: Anuphan (Thai-optimized)
Real-Time System (SSE)
| Endpoint | Events | Clients |
|---|---|---|
artifact |
submit, toggleCheck, toggleLock, setLimit, wipe | Admin panel, artifact widget |
rubgram |
submit, paid, toggleCheck, toggleLock, setLimit, setFree, cancel, uploadSlip | Admin panel, rubgram widget |
donate |
ping, heartbeat, update, refresh | Donate widget, top widget, admin |
active |
version (deploy), live (YouTube status) | All pages |
log |
update (audit log) | Admin log viewer |
tl.{name} |
update_states, update_placements | Tierlist per-type |
Embeddable Widgets (OBS Browser Source)
| Widget | Route | Description |
|---|---|---|
| Donation alert | /widget/donate |
Full-screen animated popup with SFX + TTS |
| Top donor | /widget/donate/top |
Compact real-time #1 donor bar |
| Artifact queue | /widget/artifact-count |
Submission count / capacity |
| Rubgram queue | /widget/rubgram-count |
Submission count / capacity |
All widgets are SSE-driven, auto-updating, and designed for streaming overlays.
Payment Processing
- Rubgram: PromptPay only — the selected slip provider verifies images, deduplicated by
transRef. 20-minute payment window with QR code. - Donation: TrueMoney (Sastify API voucher redemption) + PromptPay (the same selected provider). Transactions stored in
endgame.slipswith provider identity, verified amount/reference, and the full original response. Historical SlipOK records remain supported.
Switch between SlipOK (default) and Slip2Go in /admin/settings → external services → slip verification. The choice is saved globally and read from the primary database for each new check; no restart is needed. Checks already running finish with their original provider. The selector requires valid configuration before saving; there is no automatic fallback or retry.
- SlipOK uses the existing
SLIPOK_API_URLandSLIPOK_API_KEY. - Slip2Go uses
SLIP2GO_VERIFY_URL(the full image endpoint URL ending in/api/verify-slip/qr-image/info) andSLIP2GO_API_SECRET(the exact secret value sent inAuthorization). See the image verification guide and authentication guide. - Slip2Go validates the exact payment amount and the account from
PROMPTPAY_TYPE(mobile,nationalId, orewallet) andPROMPTPAY_IDENTIFIER. Rubgram's static QR must receive into that same account. Provider duplicate checking is omitted; the shared database unique reference prevents reuse across both flows and providers while allowing failed submissions to be retried.
Before deploying this change, run bun dr push against the deployment database to add settings.slipCheckProvider with default slipok. No existing slip JSON needs migration. Then configure Slip2Go credentials and PromptPay variables on each app instance before selecting Slip2Go. Revert to SlipOK using the same selector.
Verification has a 30-second timeout and returns payment errors for provider failures, invalid responses, or unavailable services. /api/health checks the selected provider's origin without uploading a slip or spending verification credits; its cache is separate per provider. This checks reachability, not bank verification or account credits.
Authentication & Authorization
- Admin: better-auth email/password with admin role check. Middleware protects
/admin/*,/artifact/admin/*,/rubgram/admin/*,/tl/*/admin/*. - Rubgram users: Discord OAuth2 — session persisted via cookie token (uuidv7).
- Internal: Redis-backed tokens (600s TTL) for backend-to-API auth via
X-Internal-Authheader. - Brute-force protection: 10 failed login attempts triggers a browser-crashing DoS defense (Brash technique) on the client.
External APIs
| API | Usage | Endpoint |
|---|---|---|
| Project Amber | Character and version data, synced every 14 days | gi.yatta.moe |
| Enka Network | Character showcase auto-fetch | enka.network |
| Astral/Enka Embed | Character card rendering | git.dgnr.us/astral/api |
| SlipOK | PromptPay slip verification | configurable |
| Slip2Go | PromptPay slip verification | configurable |
| Sastify | TrueMoney voucher redemption | api.sastify.xyz |
| Google Gemini | TTS for donation announcements | gemini-2.5-flash-preview-tts |
| Discord API | OAuth2, guild member list, webhook pings | discord.com |
| YouTube API | Live stream status | googleapis.com |
| PostHog Cloud | Analytics and error tracking | us.i.posthog.com |
Database Schemas (PostgreSQL)
| Schema | Tables | Domain |
|---|---|---|
public |
characters, versions, settings, guides, cdn, auditLog, user, session, account, verification | Core data (characters, auth, CDN, settings) |
artifact |
submissions, cards, settings | Artifact review queue |
endgame |
submissions, sarchive, expired, slips, settings, discord, types | Rubgram service ordering |
tierlist |
types, tiers, columns, badges, versions, states | Tier rankings |
donate |
donations | Donation records |
Contributing
Prerequisites
- Bun, Docker & Docker Compose
Setup
- Clone the repo.
- Copy
.env.exampleto.envand fill in secrets (or usedev/.env.developmentfor local defaults). - Start the dev environment:
This spins up PostgreSQL, Redis, the backend, and the Next.js dev server with Turbopack — hot reload via Docker sync.
bun dev - Access at
http://localhost:3000. - Seeded admin account:
[email protected]/youshallnotpass(set viaINITIAL_ADMIN_PWD).
Useful commands
| Command | What it does |
|---|---|
bun dev |
Start full dev environment |
bun ds |
Open devshell |
bun run lint |
oxlint |
bun run format |
oxfmt |
bun run build |
Production build (Turbopack) |
bun dr |
drizzle-kit (generate, push, studio) |
bun ba |
better-auth CLI |
Code style
- Linting: oxlint (with tailwind and tsgo plugins), oxfmt
- All row IDs are UUIDv7
- PostgreSQL schemas for domain separation (
public,artifact,endgame,tierlist,donate) - New features should follow existing patterns: SSE for real-time, Zod for validation, server actions for mutations
Project structure
| Path | Contents |
|---|---|
app/(ui)/ |
Public pages (donate, artifact, rubgram, tierlist, guide, admin, login) |
app/api/ |
API routes (auth, payments, TTS, Amber sync, card rendering) |
app/widget/ |
Embeddable OBS overlays |
app/sse/ |
Server-Sent Events streaming endpoints |
backend/ |
Standalone Bun backend (cron jobs, Discord webhooks, DB seeding) |
lib/db/ |
Database schema, SSE endpoint definitions, Redis client |
components/ |
Shared UI primitives (shadcn/ui) |
Deployment
CI/CD (Gitea Actions)
Push to main or dev triggers .github/workflows/build.yml:
- Build frontend Docker image (multi-stage: deps → drizzle push → builder → runner)
- Build backend Docker image (compiled to Bun bytecode via
bun build --target bun --bytecode) - Push both images to private registry at
registry.neko-piranha.ts.net - Rolling update of Docker Swarm services (
buzz_app,buzz_backend)
Production stack (Docker Swarm)
| Service | Replicas | Resources | Notes |
|---|---|---|---|
| Frontend | 2 | 1 CPU / 1 GB RAM | Healthcheck on :3000, autoscaling label |
| Backend | 1 | 1 CPU / 512 MB RAM | NO_AUTH_CHECK=true env, auto-rollback on failure |
Infrastructure dependencies:
- PostgreSQL database (managed externally)
- Redis instance for SSE pub/sub and caching
- Nginx reverse proxy with SSE optimizations (buffering off, 200s read timeout)
- Private Docker registry at
registry.neko-piranha.ts.net
Environment
Required variables (~30 total, see .env.example):
| Category | Variables |
|---|---|
| Database | DATABASE_URL, REDIS_URL |
| Auth | BETTER_AUTH_SECRET |
| Slip verification | SLIPOK_API_URL, SLIPOK_API_KEY; optional SLIP2GO_VERIFY_URL, SLIP2GO_API_SECRET |
| PromptPay account | PROMPTPAY_TYPE, PROMPTPAY_IDENTIFIER (required for Slip2Go and donation QR) |
| Donation payments | TMN_DEST_PHONE_NUM, SASTIFY_API_PRIVKEY |
| TTS | GEMINI_TTS_API_KEY |
| Widget | DONATE_WIDGET_KEY |
| Discord | DISCORD_BOT_TOKEN, DISCORD_CLIENT_*, DISCORD_GUILD_ID, DISCORD_WEBHOOK_URL |
| YouTube | YOUTUBE_CHANNEL_ID, YOUTUBE_API_KEY |
| Analytics | NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN |
Developers
Lead Developer: @dmgnr
Project Coordinator: @gunshiz
Consultant Developer: @s4msh1ne