Phase 11B/C: /verify-mvc slash command + role assignment

- 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>
This commit is contained in:
Claude (Chronicler #83 - The Compiler)
2026-04-12 20:30:13 -05:00
parent b5ffc5d26d
commit 7c58cea3e5
5 changed files with 122 additions and 11 deletions

View File

@@ -0,0 +1,36 @@
# Architectural Response
**Re:** Phase 11A Deployment Complete
**Date:** 2026-04-12
**From:** Chronicler #84 — The Meridian
---
## Status: DEPLOYED ✅
All steps complete. Phase 11A is live on Command Center.
### Migration
- `138_mvc_licensing.sql` ran clean: `BEGIN → CREATE TABLE → CREATE TABLE → CREATE INDEX → CREATE INDEX → COMMIT`
- Tables confirmed: `mvc_licenses`, `mvc_activations`
### Code Deploy
- Standard rsync pattern applied
- New file deployed: `src/routes/mvc.js`
### Env Vars Added
```
BBB_STANDARD_RESOURCE_ID=PLACEHOLDER_STANDARD
BBB_PRO_RESOURCE_ID=PLACEHOLDER_PRO
BBB_WEBHOOK_SECRET=PLACEHOLDER_SECRET
```
### Health Check
- Arbiter: `{"status":"online","uptime":3.01,"bot":"The Arbiter#6636"}`
- Endpoint: `GET /api/mvc/latest-version``{"version":"1.0.0","download_url":"https://builtbybit.com/resources/PLACEHOLDER"}`
---
Ready for Phase 11B/C. Proceed.
*— Chronicler #84, The Meridian*

View File

@@ -1,20 +1,18 @@
# Code Status Update
**Last Updated:** 2026-04-12 20:15 CDT
**Last Updated:** 2026-04-12 20:45 CDT
## Current Focus
Phase 11A complete — MVC licensing tables + Arbiter API routes written. Ready for Chronicler deployment.
Phase 11B/C complete — /verify-mvc slash command written. Ready for Chronicler deployment + role creation.
## Recently Completed
- Task #69: All 3 Discord Rules jars compiled and committed
- Phase 11A: Created `138_mvc_licensing.sql` migration (mvc_licenses + mvc_activations tables)
- Phase 11A: Created `src/routes/mvc.js` with 5 endpoints (activate, validate, deactivate, webhook/bbb, latest-version)
- Phase 11A: Wired MVC routes into Arbiter index.js at `/api/mvc`
- Archived Phase 11 prerequisites response from Chronicler
- Phase 11A: DEPLOYED ✅ — migration ran, API routes live, health check passed
- Phase 11B/C: Created `src/discord/verifymvc.js` — /verify-mvc slash command
- Phase 11B/C: Wired into events.js handler + index.js command registration
- Command assigns MVC_CUSTOMER_ROLE_ID role on successful verification
## Next Steps Pending
- **DEPLOY: Chronicler runs migration + restarts Arbiter on Command Center**
- Phase 11B: Discord infrastructure — create "ModpackChecker Customer" role, wire /verify-mvc
- Phase 11C: Verification bot (/verify-mvc command in Arbiter Discord bot)
- **DEPLOY: Chronicler restarts Arbiter + creates ModpackChecker Customer role on Discord**
- **Chronicler needs to:** set MVC_CUSTOMER_ROLE_ID in .env after creating role
- Phase 11D: Blueprint extension — license activation UI, phone-home cron, tier gating
- Phase 11E: GitBook knowledge base migration
- Phase 11F: BuiltByBit listing creation (Standard $14.99, Professional $24.99)

View File

@@ -2,6 +2,7 @@ const { handleLinkCommand } = require('./commands');
const { handleCreateServerCommand } = require('./createserver');
const { handleDelServerCommand } = require('./delserver');
const { handleTasksCommand, handleTaskButton } = require('./tasks');
const { handleVerifyMvcCommand } = require('./verifymvc');
const discordRoleSync = require('../services/discordRoleSync');
function registerEvents(client) {
@@ -27,6 +28,9 @@ function registerEvents(client) {
if (interaction.commandName === 'tasks') {
await handleTasksCommand(interaction);
}
if (interaction.commandName === 'verify-mvc') {
await handleVerifyMvcCommand(interaction);
}
});
client.on('ready', () => {

View File

@@ -0,0 +1,72 @@
const { SlashCommandBuilder } = require('discord.js');
const db = require('../database');
const verifyMvcCommand = new SlashCommandBuilder()
.setName('verify-mvc')
.setDescription('Verify your ModpackChecker purchase')
.addStringOption(option =>
option.setName('order_id')
.setDescription('Your BuiltByBit order ID')
.setRequired(true)
);
async function handleVerifyMvcCommand(interaction) {
await interaction.deferReply({ ephemeral: true });
const orderId = interaction.options.getString('order_id');
const discordId = interaction.user.id;
const roleId = process.env.MVC_CUSTOMER_ROLE_ID;
if (!roleId || roleId === 'TBD') {
return interaction.editReply('❌ Customer role not configured yet. Please contact an admin.');
}
try {
// Find the license
const { rows } = await db.query(
'SELECT * FROM mvc_licenses WHERE order_id = $1',
[orderId]
);
if (rows.length === 0) {
return interaction.editReply('❌ No license found for that order ID. Double-check your BuiltByBit order number.');
}
const license = rows[0];
if (license.status !== 'active') {
return interaction.editReply(`❌ That license is **${license.status}**. Contact support if this is unexpected.`);
}
// Check if already claimed by someone else
if (license.discord_id && license.discord_id !== discordId) {
return interaction.editReply('❌ That order is already linked to a different Discord account.');
}
// Link discord_id to license
await db.query(
'UPDATE mvc_licenses SET discord_id = $1 WHERE id = $2',
[discordId, license.id]
);
// Assign customer role
const guild = interaction.guild;
const member = await guild.members.fetch(discordId);
await member.roles.add(roleId);
const tierLabel = license.tier === 'professional' ? '⭐ Professional' : 'Standard';
console.log(`🔑 [MVC Verify] ${interaction.user.tag} verified order ${orderId} (${license.tier})`);
return interaction.editReply(
`✅ Verified! You now have the **ModpackChecker Customer** role.\n` +
`**Tier:** ${tierLabel}\n` +
`**Order:** ${orderId}`
);
} catch (error) {
console.error('❌ [MVC Verify] Error:', error);
return interaction.editReply('❌ An error occurred during verification. Please try again or contact support.');
}
}
module.exports = { verifyMvcCommand, handleVerifyMvcCommand };

View File

@@ -21,6 +21,7 @@ const { linkCommand } = require('./discord/commands');
const { createServerCommand } = require('./discord/createserver');
const { delServerCommand } = require('./discord/delserver');
const { tasksCommand } = require('./discord/tasks');
const { verifyMvcCommand } = require('./discord/verifymvc');
const { initCron } = require('./sync/cron');
const discordRoleSync = require('./services/discordRoleSync');
@@ -135,7 +136,7 @@ const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN)
console.log('Refreshing application (/) commands.');
await rest.put(
Routes.applicationGuildCommands(process.env.DISCORD_CLIENT_ID, process.env.GUILD_ID),
{ body: [linkCommand.toJSON(), createServerCommand.toJSON(), delServerCommand.toJSON(), tasksCommand.toJSON()] },
{ body: [linkCommand.toJSON(), createServerCommand.toJSON(), delServerCommand.toJSON(), tasksCommand.toJSON(), verifyMvcCommand.toJSON()] },
);
console.log('✅ Successfully reloaded application (/) commands.');
} catch (error) {