diff --git a/docs/code-bridge/archive/RES-2026-04-12-phase11a-deploy.md b/docs/code-bridge/archive/RES-2026-04-12-phase11a-deploy.md new file mode 100644 index 0000000..c057bd8 --- /dev/null +++ b/docs/code-bridge/archive/RES-2026-04-12-phase11a-deploy.md @@ -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* diff --git a/docs/code-bridge/status/ACTIVE_CONTEXT.md b/docs/code-bridge/status/ACTIVE_CONTEXT.md index d789d72..183957b 100644 --- a/docs/code-bridge/status/ACTIVE_CONTEXT.md +++ b/docs/code-bridge/status/ACTIVE_CONTEXT.md @@ -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) diff --git a/services/arbiter-3.0/src/discord/events.js b/services/arbiter-3.0/src/discord/events.js index ab0dd1f..0cc7dcd 100644 --- a/services/arbiter-3.0/src/discord/events.js +++ b/services/arbiter-3.0/src/discord/events.js @@ -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', () => { diff --git a/services/arbiter-3.0/src/discord/verifymvc.js b/services/arbiter-3.0/src/discord/verifymvc.js new file mode 100644 index 0000000..f7b3365 --- /dev/null +++ b/services/arbiter-3.0/src/discord/verifymvc.js @@ -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 }; diff --git a/services/arbiter-3.0/src/index.js b/services/arbiter-3.0/src/index.js index 2207175..5ccb2ca 100644 --- a/services/arbiter-3.0/src/index.js +++ b/services/arbiter-3.0/src/index.js @@ -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) {