diff --git a/services/arbiter-3.0/.env.example b/services/arbiter-3.0/.env.example index bba0574..7b56541 100644 --- a/services/arbiter-3.0/.env.example +++ b/services/arbiter-3.0/.env.example @@ -1,27 +1,24 @@ -# Discord -DISCORD_BOT_TOKEN=your_bot_token_here -DISCORD_CLIENT_ID=your_client_id_here -GUILD_ID=your_guild_id_here +# Discord Core +DISCORD_BOT_TOKEN=your_bot_token +GUILD_ID=your_guild_id +DISCORD_CLIENT_ID=your_client_id +DISCORD_CLIENT_SECRET=your_client_secret -# Database +# OAuth & Admin +REDIRECT_URI=https://discord-bot.firefrostgaming.com/auth/discord/callback +ADMIN_USERS=discord_id_1,discord_id_2 +SESSION_SECRET=your_secure_session_secret +PORT=3500 +NODE_ENV=production + +# PostgreSQL Database DB_USER=arbiter DB_HOST=127.0.0.1 DB_NAME=arbiter_db DB_PASSWORD=your_secure_password DB_PORT=5432 -# Pterodactyl +# Pterodactyl Integration PANEL_URL=https://panel.firefrostgaming.com PANEL_CLIENT_KEY=ptlc_... PANEL_APPLICATION_KEY=ptla_... - -# Paymenter -PAYMENTER_WEBHOOK_SECRET=your_webhook_secret - -# Admin Panel -ADMIN_USERNAME=trinity -ADMIN_PASSWORD=your_secure_admin_password - -# Application -NODE_ENV=production -PORT=3000 diff --git a/services/arbiter-3.0/README.md b/services/arbiter-3.0/README.md deleted file mode 100644 index 77fb80a..0000000 --- a/services/arbiter-3.0/README.md +++ /dev/null @@ -1,133 +0,0 @@ -# Arbiter 3.0 - Unified Access Manager - -Production-ready Discord bot + Pterodactyl whitelist manager for Firefrost Gaming. - -## Features - -- Discord `/link` slash command for Minecraft account linking -- Automatic whitelist sync to all Minecraft servers -- Paymenter webhook integration for subscription management -- Grace period handling (3 days → downgrade to Awakened) -- Hourly reconciliation cron job -- Admin panel for monitoring and manual sync - -## Installation - -```bash -npm install -``` - -## Database Setup - -1. Log into PostgreSQL: -```bash -sudo -u postgres psql -``` - -2. Create database and user: -```sql -CREATE DATABASE arbiter_db; -CREATE USER arbiter WITH ENCRYPTED PASSWORD 'your_password'; -GRANT ALL PRIVILEGES ON DATABASE arbiter_db TO arbiter; -``` - -3. Connect to database and run schema: -```bash -psql -U arbiter -d arbiter_db -``` - -4. Run the schema from the implementation guide: -```sql -CREATE TABLE users ( - discord_id VARCHAR(255) PRIMARY KEY, - minecraft_username VARCHAR(255) UNIQUE, - minecraft_uuid VARCHAR(255) UNIQUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE subscriptions ( - id SERIAL PRIMARY KEY, - discord_id VARCHAR(255) REFERENCES users(discord_id), - tier_level INT NOT NULL, - status VARCHAR(50) NOT NULL, - stripe_customer_id VARCHAR(255), - paymenter_order_id VARCHAR(255), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE server_sync_log ( - server_identifier VARCHAR(50) PRIMARY KEY, - last_successful_sync TIMESTAMP, - last_error TEXT, - is_online BOOLEAN DEFAULT true -); - -CREATE INDEX idx_users_discord_id ON users(discord_id); -CREATE INDEX idx_subscriptions_status ON subscriptions(status); -``` - -## Configuration - -Copy `.env.example` to `.env` and fill in all values: -- Discord bot token and client ID -- PostgreSQL credentials -- Pterodactyl Panel API keys (Client + Application) -- Paymenter webhook secret -- Admin panel credentials - -## Deployment (Debian 12) - -1. Move to `/opt`: -```bash -sudo cp -r services/arbiter-3.0 /opt/ -cd /opt/arbiter-3.0 -npm install --production -``` - -2. Install systemd service: -```bash -sudo cp deploy/arbiter-3.service /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable --now arbiter-3 -``` - -3. Check status: -```bash -sudo systemctl status arbiter-3 -sudo journalctl -u arbiter-3 -f -``` - -## Admin Panel - -Access at: `http://your-server:3000/admin/status` - -Endpoints: -- `GET /admin/status` - View sync logs and linked accounts -- `POST /admin/force-sync` - Trigger manual whitelist sync - -## Architecture - -- **Discord Bot**: Handles `/link` command and role assignment -- **Express Server**: Webhook handlers and admin panel -- **PostgreSQL**: Single source of truth for users and subscriptions -- **Pterodactyl Integration**: Auto-discovery and file-based whitelist sync -- **Cron**: Hourly reconciliation at minute 0 - -## Troubleshooting - -**Bot not registering slash commands?** -- Check DISCORD_CLIENT_ID and GUILD_ID in .env -- Restart the service after changes - -**Whitelist not syncing?** -- Check `/admin/status` for sync errors -- Verify Panel API keys have correct permissions -- Check server_sync_log table for error messages - -**Database connection errors?** -- Verify PostgreSQL is running -- Check DB credentials in .env -- Ensure database and user exist - -## Fire + Frost + Foundation = Where Love Builds Legacy 💙🔥❄️ diff --git a/services/arbiter-3.0/deploy/arbiter-3.service b/services/arbiter-3.0/deploy/arbiter-3.service deleted file mode 100644 index 00db976..0000000 --- a/services/arbiter-3.0/deploy/arbiter-3.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Arbiter 3.0 Unified Access Manager -After=network.target postgresql.service - -[Service] -Type=simple -User=arbiter -WorkingDirectory=/opt/arbiter-3.0 -ExecStart=/usr/bin/node src/index.js -Restart=on-failure -RestartSec=10 -Environment=NODE_ENV=production - -[Install] -WantedBy=multi-user.target diff --git a/services/arbiter-3.0/package.json b/services/arbiter-3.0/package.json index 8f36bdb..66dee12 100644 --- a/services/arbiter-3.0/package.json +++ b/services/arbiter-3.0/package.json @@ -1,17 +1,22 @@ { "name": "arbiter-3.0", "version": "3.0.0", - "description": "Unified Access Manager for Discord and Pterodactyl", + "description": "Modular Access & Role Manager", "main": "src/index.js", "scripts": { "start": "node src/index.js", "dev": "node --watch src/index.js" }, "dependencies": { + "body-parser": "^1.20.2", + "cookie-parser": "^1.4.7", "discord.js": "^14.14.1", "dotenv": "^16.4.5", - "express": "^4.19.2", + "express": "^4.18.2", + "express-session": "^1.19.0", "node-cron": "^3.0.3", + "passport": "^0.7.0", + "passport-discord": "^0.1.4", "pg": "^8.11.3" } } diff --git a/services/arbiter-3.0/role-mappings.json b/services/arbiter-3.0/role-mappings.json new file mode 100644 index 0000000..30da77f --- /dev/null +++ b/services/arbiter-3.0/role-mappings.json @@ -0,0 +1,12 @@ +{ + "fire-elemental": "", + "fire-knight": "", + "fire-master": "", + "fire-legend": "", + "frost-elemental": "", + "frost-knight": "", + "frost-master": "", + "frost-legend": "", + "the-awakened": "", + "the-sovereign": "" +} diff --git a/services/arbiter-3.0/src/admin/routes.js b/services/arbiter-3.0/src/admin/routes.js deleted file mode 100644 index 6ef147c..0000000 --- a/services/arbiter-3.0/src/admin/routes.js +++ /dev/null @@ -1,32 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const db = require('../database'); -const { triggerImmediateSync } = require('../sync/immediate'); - -// Basic Auth Middleware -const basicAuth = (req, res, next) => { - const b64auth = (req.headers.authorization || '').split(' ')[1] || ''; - const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':'); - - if (login === process.env.ADMIN_USERNAME && password === process.env.ADMIN_PASSWORD) { - return next(); - } - res.set('WWW-Authenticate', 'Basic realm="401"'); - res.status(401).send('Authentication required.'); -}; - -router.use(basicAuth); - -router.get('/status', async (req, res) => { - const { rows: logs } = await db.query('SELECT * FROM server_sync_log ORDER BY last_successful_sync DESC'); - const { rows: users } = await db.query('SELECT u.discord_id, u.minecraft_username, s.tier_level, s.status FROM users u LEFT JOIN subscriptions s ON u.discord_id = s.discord_id'); - - res.json({ sync_status: logs, users: users }); -}); - -router.post('/force-sync', async (req, res) => { - triggerImmediateSync(); - res.json({ message: "Sync triggered in background." }); -}); - -module.exports = router; diff --git a/services/arbiter-3.0/src/database.js b/services/arbiter-3.0/src/database.js index baf083d..3ea9ab9 100644 --- a/services/arbiter-3.0/src/database.js +++ b/services/arbiter-3.0/src/database.js @@ -7,7 +7,7 @@ const pool = new Pool({ database: process.env.DB_NAME, password: process.env.DB_PASSWORD, port: process.env.DB_PORT, - max: 20, + max: 20, idleTimeoutMillis: 30000 }); diff --git a/services/arbiter-3.0/src/discord/commands.js b/services/arbiter-3.0/src/discord/commands.js index 65439ea..c023f7c 100644 --- a/services/arbiter-3.0/src/discord/commands.js +++ b/services/arbiter-3.0/src/discord/commands.js @@ -5,7 +5,7 @@ const { triggerImmediateSync } = require('../sync/immediate'); const linkCommand = new SlashCommandBuilder() .setName('link') - .setDescription('Link your Minecraft account to your subscription') + .setDescription('Link your Minecraft account') .addStringOption(option => option.setName('username') .setDescription('Your exact Minecraft username') @@ -18,9 +18,8 @@ async function handleLinkCommand(interaction) { const discordId = interaction.user.id; const mojangData = await validateMinecraftUser(username); - if (!mojangData) { - return interaction.editReply(`❌ Could not find Minecraft account **${username}**. Please check the spelling.`); + return interaction.editReply(`❌ Could not find account **${username}**.`); } try { @@ -32,17 +31,11 @@ async function handleLinkCommand(interaction) { [discordId, mojangData.name, mojangData.uuid] ); - await interaction.editReply(`✅ Successfully linked **${mojangData.name}**! Syncing whitelists now...`); - - // Fire and forget sync + await interaction.editReply(`✅ Linked **${mojangData.name}**! Syncing whitelists...`); triggerImmediateSync(); - } catch (error) { - console.error("Database error during link:", error); - if (error.constraint === 'users_minecraft_username_key') { - return interaction.editReply(`❌ The Minecraft account **${mojangData.name}** is already linked to another Discord user.`); - } - interaction.editReply('❌ An internal database error occurred.'); + console.error("Database error:", error); + interaction.editReply('❌ An internal error occurred.'); } } diff --git a/services/arbiter-3.0/src/discord/events.js b/services/arbiter-3.0/src/discord/events.js index a681f1e..a8264b7 100644 --- a/services/arbiter-3.0/src/discord/events.js +++ b/services/arbiter-3.0/src/discord/events.js @@ -3,7 +3,6 @@ const { handleLinkCommand } = require('./commands'); function registerEvents(client) { client.on('interactionCreate', async interaction => { if (!interaction.isChatInputCommand()) return; - if (interaction.commandName === 'link') { await handleLinkCommand(interaction); } diff --git a/services/arbiter-3.0/src/index.js b/services/arbiter-3.0/src/index.js index 8822af3..ed7b837 100644 --- a/services/arbiter-3.0/src/index.js +++ b/services/arbiter-3.0/src/index.js @@ -1,27 +1,76 @@ require('dotenv').config(); -const { Client, GatewayIntentBits, REST, Routes } = require('discord.js'); const express = require('express'); +const session = require('express-session'); +const passport = require('passport'); +const DiscordStrategy = require('passport-discord').Strategy; +const { Client, GatewayIntentBits, REST, Routes } = require('discord.js'); + +const authRoutes = require('./routes/auth'); +const adminRoutes = require('./routes/admin'); +const webhookRoutes = require('./routes/webhook'); const { registerEvents } = require('./discord/events'); const { linkCommand } = require('./discord/commands'); const { initCron } = require('./sync/cron'); -const paymenterRoutes = require('./webhooks/paymenter'); -const adminRoutes = require('./admin/routes'); -// 1. Initialize Express -const app = express(); -app.use(express.json()); -app.use('/webhooks', paymenterRoutes); -app.use('/admin', adminRoutes); - -const PORT = process.env.PORT || 3000; -app.listen(PORT, () => console.log(`Express server running on port ${PORT}`)); - -// 2. Initialize Discord +// Initialize Discord Client const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers] }); registerEvents(client); -client.login(process.env.DISCORD_BOT_TOKEN); -// 3. Register Slash Commands +// Passport Configuration +passport.serializeUser((user, done) => done(null, user)); +passport.deserializeUser((obj, done) => done(null, obj)); + +passport.use(new DiscordStrategy({ + clientID: process.env.DISCORD_CLIENT_ID, + clientSecret: process.env.DISCORD_CLIENT_SECRET, + callbackURL: process.env.REDIRECT_URI, + scope: ['identify'] +}, (accessToken, refreshToken, profile, done) => { + return done(null, profile); +})); + +// Initialize Express App +const app = express(); +app.set('trust proxy', 1); + +// Make Discord client accessible to routes +app.locals.client = client; + +app.use(session({ + secret: process.env.SESSION_SECRET, + resave: false, + saveUninitialized: false, + cookie: { + secure: process.env.NODE_ENV === 'production', + maxAge: 7 * 24 * 60 * 60 * 1000 + } +})); +app.use(passport.initialize()); +app.use(passport.session()); + +// Health Check +app.get('/health', (req, res) => { + res.json({ + status: 'online', + uptime: process.uptime(), + bot: client.user?.tag || 'not ready' + }); +}); + +// Register Routes +app.use('/auth', authRoutes); +app.use('/admin', adminRoutes); +app.use('/webhook', webhookRoutes); + +// Start Application +const PORT = process.env.PORT || 3500; +app.listen(PORT, () => { + console.log(`🌐 Express server running on port ${PORT}`); + console.log(`📍 Admin panel: https://discord-bot.firefrostgaming.com/admin`); + client.login(process.env.DISCORD_BOT_TOKEN); +}); + +// Register Slash Commands const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN); (async () => { try { @@ -30,11 +79,17 @@ const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN) Routes.applicationGuildCommands(process.env.DISCORD_CLIENT_ID, process.env.GUILD_ID), { body: [linkCommand.toJSON()] }, ); - console.log('Successfully reloaded application (/) commands.'); + console.log('✅ Successfully reloaded application (/) commands.'); } catch (error) { - console.error(error); + console.error('Failed to register slash commands:', error); } })(); -// 4. Initialize Cron +// Initialize Hourly Cron Job initCron(); +console.log('✅ Hourly sync cron initialized.'); + +// Error handling +process.on('unhandledRejection', error => { + console.error('❌ Unhandled promise rejection:', error); +}); diff --git a/services/arbiter-3.0/src/mojang/validate.js b/services/arbiter-3.0/src/mojang/validate.js index 431ef01..08114e9 100644 --- a/services/arbiter-3.0/src/mojang/validate.js +++ b/services/arbiter-3.0/src/mojang/validate.js @@ -5,15 +5,11 @@ function formatUUID(uuidStr) { async function validateMinecraftUser(username) { try { const res = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`); - if (res.status === 204 || res.status === 404) return null; if (!res.ok) throw new Error(`Mojang API error: ${res.status}`); const data = await res.json(); - return { - name: data.name, - uuid: formatUUID(data.id) - }; + return { name: data.name, uuid: formatUUID(data.id) }; } catch (error) { console.error("Failed to validate Minecraft user:", error); return null; diff --git a/services/arbiter-3.0/src/panel/commands.js b/services/arbiter-3.0/src/panel/commands.js index dbbc3fb..5e00ee2 100644 --- a/services/arbiter-3.0/src/panel/commands.js +++ b/services/arbiter-3.0/src/panel/commands.js @@ -2,7 +2,6 @@ require('dotenv').config(); async function reloadWhitelistCommand(serverIdentifier) { const endpoint = `${process.env.PANEL_URL}/api/client/servers/${serverIdentifier}/command`; - const res = await fetch(endpoint, { method: 'POST', headers: { @@ -13,11 +12,7 @@ async function reloadWhitelistCommand(serverIdentifier) { body: JSON.stringify({ command: "whitelist reload" }) }); - if (res.status === 412) { - console.log(`[${serverIdentifier}] is offline. File saved for next boot.`); - return true; - } - + if (res.status === 412) return true; if (!res.ok) throw new Error(`Command failed: ${res.statusText}`); return true; } diff --git a/services/arbiter-3.0/src/panel/discovery.js b/services/arbiter-3.0/src/panel/discovery.js index 9dde563..57c393e 100644 --- a/services/arbiter-3.0/src/panel/discovery.js +++ b/services/arbiter-3.0/src/panel/discovery.js @@ -2,28 +2,21 @@ require('dotenv').config(); async function getMinecraftServers() { const endpoint = `${process.env.PANEL_URL}/api/application/servers?include=allocations,node,nest`; - try { const res = await fetch(endpoint, { - method: 'GET', headers: { 'Authorization': `Bearer ${process.env.PANEL_APPLICATION_KEY}`, 'Accept': 'application/json' } }); - if (!res.ok) throw new Error(`Panel API error: ${res.statusText}`); - const data = await res.json(); - // Filter out non-Minecraft servers return data.data.filter(server => { - const nestName = server.attributes.relationships?.nest?.attributes?.name; - return nestName === 'Minecraft'; + return server.attributes.relationships?.nest?.attributes?.name === 'Minecraft'; }).map(server => ({ identifier: server.attributes.identifier, - name: server.attributes.name, - node: server.attributes.relationships?.node?.attributes?.name + name: server.attributes.name })); } catch (error) { console.error("Discovery failed:", error); diff --git a/services/arbiter-3.0/src/panel/files.js b/services/arbiter-3.0/src/panel/files.js index b068da6..3f22409 100644 --- a/services/arbiter-3.0/src/panel/files.js +++ b/services/arbiter-3.0/src/panel/files.js @@ -1,9 +1,7 @@ require('dotenv').config(); async function writeWhitelistFile(serverIdentifier, whitelistArray) { - const fileContent = JSON.stringify(whitelistArray, null, 2); const endpoint = `${process.env.PANEL_URL}/api/client/servers/${serverIdentifier}/files/write?file=whitelist.json`; - const res = await fetch(endpoint, { method: 'POST', headers: { @@ -11,9 +9,8 @@ async function writeWhitelistFile(serverIdentifier, whitelistArray) { 'Accept': 'application/json', 'Content-Type': 'text/plain' }, - body: fileContent + body: JSON.stringify(whitelistArray, null, 2) }); - if (!res.ok) throw new Error(`Failed to write file: ${res.statusText}`); return true; } diff --git a/services/arbiter-3.0/src/routes/admin.js b/services/arbiter-3.0/src/routes/admin.js new file mode 100644 index 0000000..a1dc94a --- /dev/null +++ b/services/arbiter-3.0/src/routes/admin.js @@ -0,0 +1,28 @@ +const express = require('express'); +const router = express.Router(); +const { getRoleMappings, saveRoleMappings } = require('../utils/roleMappings'); + +const isAdmin = (req, res, next) => { + if (req.isAuthenticated()) { + const admins = process.env.ADMIN_USERS.split(','); + if (admins.includes(req.user.id)) return next(); + } + res.status(403).send('Forbidden: Admin access only.'); +}; + +// TODO: Replace with full beautiful UI from live bot.js +router.get('/', isAdmin, (req, res) => { + const mappings = getRoleMappings(); + res.json({ message: "Admin Panel UI", mappings }); +}); + +router.post('/mappings', isAdmin, express.json(), (req, res) => { + const newMappings = req.body; + if (saveRoleMappings(newMappings)) { + res.status(200).send('Mappings updated'); + } else { + res.status(500).send('Failed to save mappings'); + } +}); + +module.exports = router; diff --git a/services/arbiter-3.0/src/routes/auth.js b/services/arbiter-3.0/src/routes/auth.js new file mode 100644 index 0000000..6c514fe --- /dev/null +++ b/services/arbiter-3.0/src/routes/auth.js @@ -0,0 +1,19 @@ +const express = require('express'); +const passport = require('passport'); +const router = express.Router(); + +router.get('/discord', passport.authenticate('discord')); + +router.get('/discord/callback', passport.authenticate('discord', { + failureRedirect: '/' +}), (req, res) => { + res.redirect('/admin'); +}); + +router.get('/logout', (req, res) => { + req.logout(() => { + res.redirect('/'); + }); +}); + +module.exports = router; diff --git a/services/arbiter-3.0/src/routes/webhook.js b/services/arbiter-3.0/src/routes/webhook.js new file mode 100644 index 0000000..1abbc10 --- /dev/null +++ b/services/arbiter-3.0/src/routes/webhook.js @@ -0,0 +1,80 @@ +const express = require('express'); +const router = express.Router(); +const db = require('../database'); +const { triggerImmediateSync } = require('../sync/immediate'); +const { getRoleMappings } = require('../utils/roleMappings'); + +// Map product slugs to database tier levels +const SLUG_TO_TIER = { + "the-awakened": 1, + "fire-elemental": 5, + "fire-knight": 10, + "fire-master": 15, + "fire-legend": 20, + "frost-elemental": 5, + "frost-knight": 10, + "frost-master": 15, + "frost-legend": 20, + "the-sovereign": 499 +}; + +router.post('/paymenter', express.json(), async (req, res) => { + const { event, user, product } = req.body; + + if (!user || !product) { + return res.status(400).send('Missing required fields'); + } + + const discordId = user.discord_id; + const slug = product.slug; + const mappings = getRoleMappings(); + const roleId = mappings[slug]; + const tierLevel = SLUG_TO_TIER[slug] || 1; + + try { + const client = req.app.locals.client; + const guild = client.guilds.cache.get(process.env.GUILD_ID); + let member = null; + + if (guild && discordId) { + try { member = await guild.members.fetch(discordId); } catch(e) {} + } + + if (event === 'subscription.created' || event === 'subscription.renewed') { + // 1. Assign Discord Role + if (member && roleId) await member.roles.add(roleId); + + // 2. Update PostgreSQL Database + await db.query( + `INSERT INTO subscriptions (discord_id, tier_level, status) + VALUES ($1, $2, 'active') + ON CONFLICT (discord_id) DO UPDATE SET tier_level = $2, status = 'active'`, + [discordId, tierLevel] + ); + + console.log(`✅ Added role for ${slug} to ${discordId}`); + } + else if (event === 'subscription.cancelled' || event === 'subscription.expired') { + // 1. Remove Discord Role + if (member && roleId) await member.roles.remove(roleId); + + // 2. Update PostgreSQL Database + await db.query( + `UPDATE subscriptions SET status = 'cancelled' WHERE discord_id = $1`, + [discordId] + ); + + console.log(`❌ Removed role for ${slug} from ${discordId}`); + } + + // 3. Trigger Server Sync + triggerImmediateSync(); + res.status(200).send('Webhook processed'); + + } catch (error) { + console.error('Webhook error:', error); + res.status(500).send('Error processing webhook'); + } +}); + +module.exports = router; diff --git a/services/arbiter-3.0/src/sync/cron.js b/services/arbiter-3.0/src/sync/cron.js index 90094fc..9f3971f 100644 --- a/services/arbiter-3.0/src/sync/cron.js +++ b/services/arbiter-3.0/src/sync/cron.js @@ -6,7 +6,6 @@ function initCron() { console.log("Starting hourly whitelist reconciliation..."); await triggerImmediateSync(); }); - console.log("Hourly sync cron initialized."); } module.exports = { initCron }; diff --git a/services/arbiter-3.0/src/sync/immediate.js b/services/arbiter-3.0/src/sync/immediate.js index fea93d5..a0654ae 100644 --- a/services/arbiter-3.0/src/sync/immediate.js +++ b/services/arbiter-3.0/src/sync/immediate.js @@ -10,7 +10,7 @@ async function triggerImmediateSync() { `SELECT minecraft_username as name, minecraft_uuid as uuid FROM users JOIN subscriptions ON users.discord_id = subscriptions.discord_id - WHERE subscriptions.status IN ('active', 'grace_period')` + WHERE subscriptions.status = 'active'` ); const servers = await getMinecraftServers(); @@ -32,7 +32,6 @@ async function triggerImmediateSync() { ); } } - console.log("Immediate sync complete."); } catch (error) { console.error("Critical failure during immediate sync:", error); } diff --git a/services/arbiter-3.0/src/utils/roleMappings.js b/services/arbiter-3.0/src/utils/roleMappings.js new file mode 100644 index 0000000..dc21790 --- /dev/null +++ b/services/arbiter-3.0/src/utils/roleMappings.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const path = require('path'); + +const MAPPINGS_FILE = path.join(__dirname, '../../role-mappings.json'); + +function getRoleMappings() { + try { + if (!fs.existsSync(MAPPINGS_FILE)) { + return {}; + } + const data = fs.readFileSync(MAPPINGS_FILE, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading role mappings:', error); + return {}; + } +} + +function saveRoleMappings(mappings) { + try { + fs.writeFileSync(MAPPINGS_FILE, JSON.stringify(mappings, null, 2)); + return true; + } catch (error) { + console.error('Error saving role mappings:', error); + return false; + } +} + +module.exports = { getRoleMappings, saveRoleMappings }; diff --git a/services/arbiter-3.0/src/webhooks/paymenter.js b/services/arbiter-3.0/src/webhooks/paymenter.js deleted file mode 100644 index 16072e5..0000000 --- a/services/arbiter-3.0/src/webhooks/paymenter.js +++ /dev/null @@ -1,53 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const db = require('../database'); -const { triggerImmediateSync } = require('../sync/immediate'); - -const TIERS = { - 1: { name: 'Awakened', roleId: 'DISCORD_ROLE_ID_1', price: 1 }, - 5: { name: 'Elemental', roleId: 'DISCORD_ROLE_ID_5', price: 5 }, - 10: { name: 'Knight', roleId: 'DISCORD_ROLE_ID_10', price: 10 }, - 15: { name: 'Master', roleId: 'DISCORD_ROLE_ID_15', price: 15 }, - 20: { name: 'Legend', roleId: 'DISCORD_ROLE_ID_20', price: 20 }, - 499: { name: 'Sovereign', roleId: 'DISCORD_ROLE_ID_499', price: 499 } -}; - -router.post('/paymenter', async (req, res) => { - const payload = req.body; - - if (req.headers['authorization'] !== process.env.PAYMENTER_WEBHOOK_SECRET) { - return res.status(401).send('Unauthorized'); - } - - const discordId = payload.discord_id; - const tierLevel = payload.tier_level; - const eventType = payload.event; - - try { - if (eventType === 'subscription_created' || eventType === 'payment_success') { - await db.query( - `INSERT INTO subscriptions (discord_id, tier_level, status) - VALUES ($1, $2, 'active') - ON CONFLICT (discord_id) DO UPDATE SET tier_level = $2, status = 'active'`, - [discordId, tierLevel] - ); - // NOTE: In production, you would fetch the GuildMember and assign TIERS[tierLevel].roleId here - } - else if (eventType === 'subscription_cancelled') { - await db.query( - `UPDATE subscriptions SET status = 'grace_period' WHERE discord_id = $1`, - [discordId] - ); - } - - // Sync after DB updates - triggerImmediateSync(); - res.status(200).send('Webhook processed'); - - } catch (error) { - console.error('Webhook processing error:', error); - res.status(500).send('Internal Server Error'); - } -}); - -module.exports = router;