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 💙❄️
- Added platform_post_id VARCHAR(255) for Phase 2 API matching
- Added composite index on (platform, platform_post_id)
- Future-proofs manual entries for automatic API sync
Chronicler #76 (per Gemini consultation)
- Add database migration for social_posts and social_account_snapshots tables
- Create /admin/social route with full CRUD for posts
- Add dashboard view with platform tabs (TikTok, Facebook, Instagram, X, Bluesky)
- Add post entry form matching TikTok Studio metrics
- Add post detail view with update capability
- Add account snapshot form for follower/demographic tracking
- Register social router in admin index
Phase 1: Manual entry dashboard
Phase 2 (future): API integration when approved
Chronicler #76
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
If user has existing session from Trinity Console login,
/stripe/auth now redirects directly to /stripe/checkout
instead of re-triggering Discord OAuth.
Chronicler #75
Session was being lost between /stripe/auth and /auth/discord/callback.
Now passes tier through Discord OAuth state parameter which survives
the redirect.
Chronicler #75
THE BUG: Website redirected to /stripe/auth but route didn't exist.
Checkout sessions were created WITHOUT client_reference_id (Discord ID),
so webhook couldn't sync Discord roles after payment.
THE FIX:
- Added GET /stripe/auth - stores tier in session, redirects to Discord OAuth
- Added GET /stripe/checkout - creates checkout WITH client_reference_id
- Updated auth callback to redirect to /stripe/checkout after OAuth
- Legacy POST /create-checkout-session kept for compatibility
FLOW NOW:
1. User clicks Subscribe on website
2. → /stripe/auth?tier=X (stores tier, redirects to Discord)
3. → /auth/discord (Discord OAuth)
4. → /auth/discord/callback (user authenticated)
5. → /stripe/checkout?tier=X (creates Stripe session WITH Discord ID)
6. → Stripe Checkout (user pays)
7. → Webhook receives event with client_reference_id
8. → Discord role synced!
Chronicler #75
- Maps Pterodactyl servers to Discord categories
- Renames channels to consistent naming convention
- DRY_RUN=true by default (preview mode)
- 1.5s rate limit delay between operations
- Snart Doctrine: built to adapt when reality hits
- 'Homestead - A Cozy Survival Experience' now matches 'homestead-chat'
- 'All The Mons (Private) - TX' now matches 'all-the-mons-chat'
- Strips subtitles after ' - ' and removes parentheticals
- Checks for 4 channels per server: chat, in-game, forum, voice
- Shows 'All 4 channels configured' or lists missing channels
- Caches Discord channel data for 5 minutes to reduce API calls
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
Phase 1 test for Task #98 Discord automation.
Creates one test category + one forum with tags + welcome post.
Includes DRY_RUN mode and permission checks.
Chronicler: #71
- New sidebar entry for Discord
- Full server structure visualization
- Channel tree with expandable categories
- Role hierarchy with color badges
- Health checks (orphan channels, empty roles, bot roles)
- Search/filter across channels and roles
- Click channel to see permission overwrites
- Click role to see explicit channel access
- Responsive design with modal details view
Chronicler: #70
New endpoints for Trinity Console:
- GET /admin/discord/audit — Full server audit (channels, roles, structure)
- GET /admin/discord/channels — Just channels
- GET /admin/discord/roles — Just roles
Returns:
- Server info (name, member count, features)
- Categories with nested children
- Orphan channels (not in categories)
- Role hierarchy with positions and member counts
- Permission overwrites per channel
Uses existing Discord.js client from app.locals.
Chronicler #70
When hourly sync encounters servers that fail (e.g., mid-restart):
- Logs the failure count
- Schedules automatic retry in 10 minutes
- Retry only targets previously failed servers
- Clears error state on successful retry
Fixes issue where servers in daily restart would stay in error state
until manual intervention.
Chronicler #69
- Queries server_sync_log for most recent successful sync
- Displays date (e.g., 'Apr 8') and time (e.g., '3:28 AM')
- Shows yellow dash with 'Never' if no syncs recorded
Chronicler #69
Added missing dependencies that were installed on server but not in repo:
- axios: ^1.14.0
- connect-pg-simple: ^10.0.0
- date-fns: ^4.1.0
This caused deploy script to fail with MODULE_NOT_FOUND errors.
Chronicler #69
The subscriptions table uses:
- tier_level (integer) not tier_id
- mrr_value (pre-calculated) not joined to subscription_tiers
- is_lifetime (boolean) not status='lifetime'
Chronicler #69
Dashboard was showing hardcoded values:
- Servers Online: 12 (should be 22)
- Active Subscribers: 0
- Total MRR: $0
Now fetches live data:
- Server count from Pterodactyl API via getMinecraftServers()
- Subscriber count and MRR from arbiter_db subscriptions table
Files changed:
- src/routes/admin/index.js: async dashboard route with data fetching
- src/views/admin/dashboard.ejs: EJS variables instead of hardcoded values
Chronicler #69