diff --git a/services/arbiter-3.0/src/routes/admin/constants.js b/services/arbiter-3.0/src/routes/admin/constants.js new file mode 100644 index 0000000..26798b0 --- /dev/null +++ b/services/arbiter-3.0/src/routes/admin/constants.js @@ -0,0 +1,14 @@ +const TIER_INFO = { + 1: { name: 'The Awakened', mrr: 1.00, path: 'universal' }, + 5: { name: 'Fire Elemental', mrr: 5.00, path: 'fire' }, + 10: { name: 'Fire Knight', mrr: 10.00, path: 'fire' }, + 15: { name: 'Fire Master', mrr: 15.00, path: 'fire' }, + 20: { name: 'Fire Legend', mrr: 20.00, path: 'fire' }, + 105: { name: 'Frost Elemental', mrr: 5.00, path: 'frost' }, + 110: { name: 'Frost Knight', mrr: 10.00, path: 'frost' }, + 115: { name: 'Frost Master', mrr: 15.00, path: 'frost' }, + 120: { name: 'Frost Legend', mrr: 20.00, path: 'frost' }, + 499: { name: 'The Sovereign', mrr: 0.00, path: 'universal', lifetime: true } +}; + +module.exports = { TIER_INFO }; diff --git a/services/arbiter-3.0/src/routes/admin/index.js b/services/arbiter-3.0/src/routes/admin/index.js index 2f1a4b2..f5bef86 100644 --- a/services/arbiter-3.0/src/routes/admin/index.js +++ b/services/arbiter-3.0/src/routes/admin/index.js @@ -1,9 +1,23 @@ -// Trinity Console - Main Admin Router -// This file will be populated by Gemini - const express = require('express'); const router = express.Router(); +const { requireTrinityAccess } = require('./middleware'); -// TODO: Gemini will provide complete implementation +// Sub-routers (We will populate these as we go) +const playersRouter = require('./players'); +// const serversRouter = require('./servers'); +// const financialsRouter = require('./financials'); + +router.use(requireTrinityAccess); + +router.get('/', (req, res) => { + res.redirect('/admin/dashboard'); +}); + +router.get('/dashboard', (req, res) => { + res.render('admin/dashboard', { title: 'Command Bridge' }); +}); + +router.use('/players', playersRouter); +// router.use('/servers', serversRouter); module.exports = router; diff --git a/services/arbiter-3.0/src/routes/admin/middleware.js b/services/arbiter-3.0/src/routes/admin/middleware.js new file mode 100644 index 0000000..72dbfba --- /dev/null +++ b/services/arbiter-3.0/src/routes/admin/middleware.js @@ -0,0 +1,17 @@ +function requireTrinityAccess(req, res, next) { + if (!req.isAuthenticated()) { + return res.redirect('/auth/discord'); + } + + const admins = (process.env.ADMIN_USERS || '').split(','); + if (!admins.includes(req.user.id)) { + return res.status(403).send('Forbidden: Trinity Access Only.'); + } + + // Inject user and current path into all EJS views + res.locals.adminUser = req.user; + res.locals.currentPath = req.path; + next(); +} + +module.exports = { requireTrinityAccess }; diff --git a/services/arbiter-3.0/src/routes/admin/players.js b/services/arbiter-3.0/src/routes/admin/players.js new file mode 100644 index 0000000..51bb3f4 --- /dev/null +++ b/services/arbiter-3.0/src/routes/admin/players.js @@ -0,0 +1,35 @@ +const express = require('express'); +const router = express.Router(); +const db = require('../../database'); +const { TIER_INFO } = require('./constants'); + +router.get('/', async (req, res) => { + // Render the shell. HTMX will fetch the table body immediately. + res.render('admin/players/index', { title: 'Player Management', tiers: TIER_INFO }); +}); + +// HTMX Endpoint for the table body (Handles pagination, sorting, searching) +router.get('/table', async (req, res) => { + const page = parseInt(req.query.page) || 1; + const limit = 20; + const offset = (page - 1) * limit; + const search = req.query.search || ''; + + // Basic search implementation + let query = ` + SELECT u.discord_id, u.minecraft_username, u.minecraft_uuid, + s.tier_level, s.status, s.updated_at + FROM users u + LEFT JOIN subscriptions s ON u.discord_id = s.discord_id + WHERE u.minecraft_username ILIKE $1 OR u.discord_id ILIKE $1 + ORDER BY s.updated_at DESC + LIMIT $2 OFFSET $3 + `; + + const { rows: players } = await db.query(query, [`%${search}%`, limit, offset]); + + // Render just the partial table rows + res.render('admin/players/_table_body', { players, TIER_INFO, page, search }); +}); + +module.exports = router; diff --git a/services/arbiter-3.0/src/views/admin/dashboard.ejs b/services/arbiter-3.0/src/views/admin/dashboard.ejs new file mode 100644 index 0000000..266da27 --- /dev/null +++ b/services/arbiter-3.0/src/views/admin/dashboard.ejs @@ -0,0 +1,30 @@ +<%- include('../layout', { body: ` +
+
+
Active Subscribers
+
0
+
+
+
Total MRR
+
$0
+
+
+
Servers Online
+
12
+
+
+
Last Sync
+
+
+
+ +
+

🔥❄️ Welcome to Trinity Console

+

+ The command center for Firefrost Gaming. Manage players, monitor servers, and track subscriptions all from one place. +

+

+ Fire + Frost + Foundation = Where Love Builds Legacy +

+
+`}) %> diff --git a/services/arbiter-3.0/src/views/admin/players/_table_body.ejs b/services/arbiter-3.0/src/views/admin/players/_table_body.ejs new file mode 100644 index 0000000..5a22d58 --- /dev/null +++ b/services/arbiter-3.0/src/views/admin/players/_table_body.ejs @@ -0,0 +1,45 @@ +<% if (players.length === 0) { %> + No players found. +<% } %> + +<% players.forEach(player => { %> + + <%= player.discord_id %> + +
+ Skin +
+
<%= player.minecraft_username || 'Unlinked' %>
+
+
+ + + <% const tier = TIER_INFO[player.tier_level] || { name: 'None', path: 'universal' }; %> + + <%= tier.name %> + + + + + + <%= player.status || 'Unknown' %> + + + + + + +<% }) %> + + + + + + diff --git a/services/arbiter-3.0/src/views/admin/players/index.ejs b/services/arbiter-3.0/src/views/admin/players/index.ejs new file mode 100644 index 0000000..1b41a6d --- /dev/null +++ b/services/arbiter-3.0/src/views/admin/players/index.ejs @@ -0,0 +1,36 @@ +<%- include('../../layout', { body: ` +
+
+ + +
+ +
+
+ +
+ + + + + + + + + + + + + +
Discord IDMinecraft ProfileSubscription TierStatusActions
Loading players...
+
+
+`}) %> diff --git a/services/arbiter-3.0/src/views/layout.ejs b/services/arbiter-3.0/src/views/layout.ejs new file mode 100644 index 0000000..69899d3 --- /dev/null +++ b/services/arbiter-3.0/src/views/layout.ejs @@ -0,0 +1,74 @@ + + + + + + <%= title %> | Trinity Console + + + + + +
+ + + +
+
+

<%= title %>

+
+ + + 🔔 0 + +
+
+ +
+ <%- body %> +
+
+
+ +