Adds manifest.json, sw.js (static-only caching, never admin routes),
placeholder cyan T icons (192 + 512), express.static middleware, and
PWA meta tags + service worker registration in layout.ejs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New discord/verifymvc.js: verifies BBB order, links discord_id, assigns customer role
- Wired into events.js handler and index.js command registration
- Reads MVC_CUSTOMER_ROLE_ID from env — Chronicler creates role + sets value
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Task #108 Phase 2: Social Analytics Automation
New endpoints (token-based auth via INTERNAL_API_TOKEN):
- POST /api/internal/social/sync - Upsert single post metrics
- POST /api/internal/social/sync/batch - Batch upsert (max 100 posts)
- POST /api/internal/social/snapshot - Upsert account-level stats
- GET /api/internal/social/digest - Summary data for Discord webhook
Architecture:
- Rube MCP -> n8n -> Arbiter /api/internal/* -> PostgreSQL
- Bearer token auth (not session-based)
- COALESCE for partial updates (only update provided fields)
Next: Generate INTERNAL_API_TOKEN and add to .env on Command Center
🔥 Fire + Frost + Foundation = Where Love Builds Legacy 💙❄️
New service that polls Pterodactyl (via servers-api Worker) every 5 min
and posts/updates status embeds in each game server's -status channel.
- Creates discord_status_messages table to persist message IDs
- Maps server names to channel IDs for all 15 game servers
- Posts new embed or edits existing one (no spam)
- Handles message deletion gracefully (re-posts if needed)
Status channels created:
- stoneblock-4-status, society-sunlit-valley-status, atm10-tts-status
- all-the-mons-status, mythcraft-5-status, beyond-depth-status
- beyond-ascension-status, otherworld-status, deceasedcraft-status
- submerged-2-status, sneaks-pirate-pack-status, cottage-witch-status
- farm-crossing-5-status, homestead-status, wolds-vaults-status
Chronicler #75
Deletes complete server setup:
- All channels in category
- The category itself
- The server role
Requires confirm:True to execute.
Without confirm, shows preview of what would be deleted.
Reminds to clean up Carl-bot reaction roles.
Staff only.
Chronicler: #71
WHAT THIS ADDS:
- Discord role sync on new subscriptions (checkout.session.completed)
- Discord role removal on chargebacks (charge.dispute.created)
- Grace period expiration job (hourly cron check)
- Automatic downgrade to Awakened when grace period expires
NEW FILES:
- src/services/discordRoleSync.js - Role add/remove/sync functions
- src/sync/graceExpiration.js - Grace period expiration processor
MODIFIED FILES:
- src/routes/stripe.js - Added role sync calls to webhook handlers
- src/discord/events.js - Initialize role sync service on bot ready
- src/sync/cron.js - Added grace period check to hourly job
- src/index.js - Import discordRoleSync service
PHILOSOPHY:
'We Don't Kick People Out' - expired grace periods downgrade to
permanent Awakened tier (tier 1, lifetime). Users keep community
access, just lose premium perks.
ROLE MAPPING (tier_level -> role key):
1=the-awakened, 2=fire-elemental, 3=frost-elemental,
4=fire-knight, 5=frost-knight, 6=fire-master, 7=frost-master,
8=fire-legend, 9=frost-legend, 10=the-sovereign
CHARGEBACKS:
- Immediate role removal
- Added to banned_users table
- Full audit logging
Signed-off-by: Claude (Chronicler #62) <claude@firefrostgaming.com>
Replaces MemoryStore with connect-pg-simple.
Sessions now persist across Arbiter restarts.
Table 'session' auto-created if missing.
Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
ISSUE:
We accidentally built a flat admin.js that replaced the working modular system
All modules (Players, Servers, Grace, Audit, Roles, Financials) exist in /routes/admin/
FIX:
1. Backed up flat admin.js to admin-backup-chronicler57.js
2. Changed index.js to require('./routes/admin/index')
3. Restored proper modular structure
MODULES RESTORED:
- /admin/players - Full CRUD with detail view, tier changes, staff toggle
- /admin/servers - Server matrix with Pterodactyl integration
- /admin/grace - Grace period management
- /admin/audit - Audit log feed
- /admin/roles - Role diagnostics
- /admin/financials - Revenue analytics
ALL ORIGINAL FUNCTIONALITY RESTORED
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
ISSUE (per Gemini consultation):
express-ejs-layouts was wrapping HTMX AJAX responses in full layout
Caused crashes because HTMX endpoints don't pass layout variables
FIX:
Added middleware to detect HX-Request header
Sets res.locals.layout = false for HTMX requests
HTMX endpoints now return raw HTML fragments
This fixes all 5 admin modules:
- Servers Matrix
- Player Management
- Grace Period (with real DB query)
- Audit Log
- Role Diagnostics
Credit: Gemini consultation - HTMX middleware pattern
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
Need BOTH mounts:
- /stripe/webhook (line 43, BEFORE json parser) - raw body for signature
- /stripe (line 83, AFTER json parser) - parsed body for checkout
This allows:
- Webhook at /stripe/webhook with raw body
- Checkout at /stripe/create-checkout-session with parsed body
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
ISSUE:
Webhook signature verification failing with 'Payload was provided as a
parsed JavaScript object instead of raw Buffer'
CAUSE:
Line 43: app.use('/stripe/webhook', stripeRoutes) - raw body
Line 83: app.use('/stripe', stripeRoutes) - JSON parsed body
Same router mounted twice at different paths caused JSON parser to run
FIX:
Removed line 83 duplicate mount
Webhook stays at /stripe/webhook with raw body parsing
Checkout stays at /stripe/create-checkout-session
FILES MODIFIED:
- services/arbiter-3.0/src/index.js (-1 line)
TESTING:
Complete Stripe test checkout, webhook should process successfully
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
Moved CORS middleware from index.js to stripe.js route handler to fix
'No Access-Control-Allow-Origin header' error.
ISSUE:
- CORS middleware in index.js was registered BEFORE routes
- Routes registered later overrode CORS settings
- Browser showed: 'No Access-Control-Allow-Origin header is present'
ROOT CAUSE:
Line 50: CORS middleware for /stripe/create-checkout-session
Line 91: app.use('/stripe', stripeRoutes) - registered AFTER CORS
Result: Routes don't inherit CORS settings from middleware above them
FIX:
- Added cors import to src/routes/stripe.js
- Applied CORS directly to create-checkout-session route handler
- Removed CORS middleware from src/index.js
- Now CORS is part of the route definition itself
FILES MODIFIED:
- services/arbiter-3.0/src/routes/stripe.js (+11 lines, CORS config)
- services/arbiter-3.0/src/index.js (-7 lines, removed middleware)
TESTING:
- Subscribe button should now successfully call endpoint
- Browser console should show 200 response, not CORS error
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
Fixed CORS to properly handle both www and non-www origins plus OPTIONS preflight.
ISSUE:
- Subscribe button click from website showed error alert
- No request logged in Trinity Console (request blocked by CORS)
- Original config only allowed https://firefrostgaming.com (no www)
- Missing OPTIONS method for preflight requests
FIX:
- Added both origins: firefrostgaming.com and www.firefrostgaming.com
- Added OPTIONS method for CORS preflight handling
- Added optionsSuccessStatus: 200 for legacy browser support
FILES MODIFIED:
- services/arbiter-3.0/src/index.js (CORS config, 3 lines changed)
TESTING:
- Deploy to Command Center
- Test subscribe button from firefrostgaming.com
- Should now successfully create Stripe checkout session
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
Added CORS middleware to allow website (firefrostgaming.com) to call
Trinity Console's /stripe/create-checkout-session endpoint.
WHAT WAS DONE:
- Installed cors package (npm install cors)
- Added cors import to src/index.js
- Configured CORS middleware for /stripe/create-checkout-session route
- Restricted to POST method only from firefrostgaming.com origin
- Positioned after body parsers, before session middleware
WHY:
- Gemini consultation verdict: Option 2 (JavaScript checkout) required
- Prevents double-click danger (users creating multiple checkout sessions)
- Enables instant button disable + loading state for better UX
- Industry standard for payment flows per Stripe documentation
FILES MODIFIED:
- services/arbiter-3.0/package.json (+cors dependency)
- services/arbiter-3.0/package-lock.json (dependency tree)
- services/arbiter-3.0/src/index.js (CORS middleware, 8 lines added)
RELATED TASKS:
- Soft launch blocker: Website subscribe button integration
- Next step: Update subscribe.njk with JavaScript checkout handler
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
WHAT WAS DONE:
- Created src/routes/stripe.js with 3 endpoints:
* POST /stripe/create-checkout-session (dynamic mode: subscription or payment)
* POST /stripe/webhook (signature verified, transaction-safe, idempotent)
* POST /stripe/create-portal-session (Stripe Customer Portal access)
- Updated package.json to add stripe@^14.14.0 dependency
- Updated src/index.js to register Stripe routes (webhook BEFORE body parsers - critical!)
- Updated .env.example with STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, BASE_URL
WHY:
- Eliminates Paymenter dependency (Gemini-approved architecture)
- Handles both recurring subscriptions (tiers 2-9) and one-time payments (Awakened, Sovereign)
- Webhook processes 8 event types with full transaction safety
- Grace period system for failed payments (3-day countdown, auto-downgrade to Awakened)
- Chargeback = immediate permanent ban
- Idempotency protection via webhook_events_processed table
TECHNICAL DETAILS:
- Checkout dynamically switches mode based on billing_type (recurring vs one-time)
- Webhook uses BEGIN/COMMIT/ROLLBACK for all database operations
- Raw body parser for webhook signature verification (must come before express.json())
- Supports Stripe Customer Portal for self-service subscription management
- Handles both stripe_subscription_id and stripe_payment_intent_id correctly
- Grace period logic excludes lifetime users (is_lifetime = TRUE)
FILES CHANGED:
- services/arbiter-3.0/src/routes/stripe.js (new, 421 lines)
- services/arbiter-3.0/package.json (added stripe dependency)
- services/arbiter-3.0/src/index.js (registered stripe routes, webhook ordering)
- services/arbiter-3.0/.env.example (added Stripe env vars)
NEXT STEPS:
- Deploy to Command Center
- Add STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET to production .env
- Configure Stripe webhook endpoint in Dashboard
- Test end-to-end in test mode
- Switch to live mode for launch
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
WHAT WAS DONE:
- Added app.get('/', ...) route handler that redirects to /admin
- Placed after health check, before CSRF middleware
WHY:
- Holly and Meg were getting 'cannot GET /' error when accessing
discord-bot.firefrostgaming.com without the /admin path
- Michael had an active session from earlier testing so didn't notice
- Trinity Console only had /admin routes defined, no root handler
HOW IT WORKS:
- Users visiting https://discord-bot.firefrostgaming.com/ now auto-redirect
to https://discord-bot.firefrostgaming.com/admin
- Simplifies access - no need to remember /admin suffix
IMPACT:
- Fixes immediate access issue for The Trinity
- Better UX - root domain works as expected
- No security impact - still requires Discord OAuth
FILES MODIFIED:
- services/arbiter-3.0/src/index.js (2 lines added)
TESTED:
- Deployed to Command Center (63.143.34.217)
- Service restarted successfully
- Holly and Meg can now access Trinity Console
Signed-off-by: Claude (Chronicler #52) <claude@firefrostgaming.com>
CRITICAL SECURITY FIX - Prevents Cross-Site Request Forgery attacks
Changes:
- Installed csurf middleware (session-based tokens)
- Added CSRF middleware to all /admin routes in src/index.js
- Configured admin router to pass csrfToken to all views
- Updated layout.ejs to send CSRF token with htmx requests
- Added EJS view engine configuration
- Added body parsing middleware (json + urlencoded)
Security Impact:
- Prevents malicious sites from executing admin actions using cookies
- All POST requests now require valid CSRF token
- Invalid tokens return 403 Forbidden
- Session-based tokens (no cookies needed)
Protected Routes:
- /admin/servers/:id/sync (force whitelist sync)
- /admin/servers/:id/toggle-whitelist (whitelist toggle)
- /admin/grace/:id/extend (grace period extension)
- /admin/grace/:id/manual (manual payment override)
- /admin/roles/resync/:id (role assignment)
Attack Scenario Prevented:
User visits malicious site while logged into Trinity Console
→ Site tries to submit form to admin endpoint
→ Request includes session cookie but NO CSRF token
→ Server rejects with 403 Forbidden
→ Attack failed!
Note: csurf is deprecated but still functional. For future refactor,
consider csrf-csrf or Express 5 built-in protection.
Refs: TRINITY-CONSOLE-PRE-LAUNCH-CHECKLIST.md - Fix#1
Chronicler: #51
Signed-off-by: Claude (Chronicler #51) <claude@firefrostgaming.com>