feat: LuckPerms meta sync on Stripe checkout — sets maxclaims/maxchunkloaders across all online servers

This commit is contained in:
Claude
2026-04-15 09:21:14 +00:00
parent 224579a5ea
commit 6bf9e41242
2 changed files with 121 additions and 0 deletions

View File

@@ -13,6 +13,7 @@ const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const db = require('../database');
const { syncRole, removeAllRoles, downgradeToAwakened } = require('../services/discordRoleSync');
const { welcomeNewMember } = require('../services/awakenedConcierge');
const { syncLuckPermsMeta } = require('../services/luckpermsSync');
// CORS configuration for checkout endpoint
const corsOptions = {
@@ -286,6 +287,15 @@ router.post('/webhook', express.raw({ type: 'application/json' }), async (req, r
welcomeNewMember(discordId, client).catch(err =>
console.error('[Concierge] Background welcome error:', err.message)
);
// Sync LuckPerms meta across all online servers (non-blocking)
syncLuckPermsMeta(discordId, tierLevel).then(result => {
if (result.success) {
console.log(`⚔️ LP meta sync for ${result.username}: tier ${tierLevel}, ${result.serversUpdated} servers`);
} else {
console.log(`⚠️ LP meta sync skipped for ${discordId}: ${result.reason}`);
}
}).catch(err => console.error('[LPSync] Background sync error:', err.message));
}
break;

View File

@@ -0,0 +1,111 @@
/**
* LuckPerms Meta Sync Service
* Sets maxclaims and maxchunkloaders on all online servers when a subscriber's tier changes.
*
* Requires: minecraft_username linked via /link command (stored in users table)
* Uses: Pterodactyl sendCommand API to run lp commands on each online server
*
* Date: April 15, 2026 (Chronicler #91, Launch Day)
*/
const db = require('../database');
const { sendCommand, getServerResources } = require('./pterodactyl');
// Tier level -> LuckPerms meta values
const TIER_META = {
1: { maxclaims: 90, maxchunkloaders: 0 }, // Awakened
2: { maxclaims: 25, maxchunkloaders: 0 }, // Elemental
3: { maxclaims: 25, maxchunkloaders: 0 }, // Elemental (Fire/Frost split tiers)
4: { maxclaims: 49, maxchunkloaders: 4 }, // Knight
5: { maxclaims: 49, maxchunkloaders: 4 }, // Knight
6: { maxclaims: 100, maxchunkloaders: 9 }, // Master
7: { maxclaims: 100, maxchunkloaders: 9 }, // Master
8: { maxclaims: 121, maxchunkloaders: 16 }, // Legend
9: { maxclaims: 121, maxchunkloaders: 16 }, // Legend
10: { maxclaims: 225, maxchunkloaders: 81 }, // Sovereign
};
/**
* Get all online Minecraft server identifiers from server_config
*/
async function getOnlineServers() {
const result = await db.query(
`SELECT server_identifier FROM server_config WHERE server_identifier IS NOT NULL`
);
const identifiers = result.rows.map(r => r.server_identifier);
// Filter to only running servers
const online = [];
await Promise.all(identifiers.map(async (id) => {
try {
const res = await getServerResources(id);
if (res.state === 'running') online.push(id);
} catch (e) {
// Server unreachable — skip silently
}
}));
return online;
}
/**
* Sync LuckPerms meta for a subscriber across all online servers.
* Called after Stripe checkout or tier change.
*
* @param {string} discordId - Discord user ID
* @param {number} tierLevel - Subscription tier level (1-10)
*/
async function syncLuckPermsMeta(discordId, tierLevel) {
const meta = TIER_META[tierLevel];
if (!meta) {
console.warn(`[LPSync] Unknown tier level ${tierLevel} for ${discordId} — skipping`);
return { success: false, reason: 'unknown_tier' };
}
// Look up Minecraft username
const userResult = await db.query(
'SELECT minecraft_username FROM users WHERE discord_id = $1',
[discordId]
);
if (userResult.rows.length === 0 || !userResult.rows[0].minecraft_username) {
console.log(`[LPSync] No Minecraft username linked for Discord ID ${discordId} — skipping LP meta sync`);
return { success: false, reason: 'no_minecraft_link' };
}
const username = userResult.rows[0].minecraft_username;
// Get online servers
const onlineServers = await getOnlineServers();
if (onlineServers.length === 0) {
console.warn(`[LPSync] No online servers found — LP meta sync skipped for ${username}`);
return { success: false, reason: 'no_online_servers' };
}
// Send meta commands to all online servers
const results = await Promise.allSettled(
onlineServers.flatMap(serverId => [
sendCommand(serverId, `lp user ${username} meta set maxclaims ${meta.maxclaims}`),
sendCommand(serverId, `lp user ${username} meta set maxchunkloaders ${meta.maxchunkloaders}`)
])
);
const succeeded = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`[LPSync] Meta sync for ${username} (tier ${tierLevel}): ${succeeded} OK, ${failed} failed across ${onlineServers.length} servers`);
console.log(`[LPSync] maxclaims=${meta.maxclaims}, maxchunkloaders=${meta.maxchunkloaders}`);
return {
success: true,
username,
tierLevel,
meta,
serversUpdated: onlineServers.length,
commandsSucceeded: succeeded,
commandsFailed: failed
};
}
module.exports = { syncLuckPermsMeta };