import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, Colors, userMention, roleMention, ChatInputCommandInteraction } from "discord.js"; import { featureFlagsService } from "@shared/modules/feature-flags/feature-flags.service"; import { createBaseEmbed, createErrorEmbed, createSuccessEmbed } from "@lib/embeds"; import { withCommandErrorHandling } from "@lib/commandUtils"; export const featureflags = createCommand({ data: new SlashCommandBuilder() .setName("featureflags") .setDescription("Manage feature flags for beta testing") .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addSubcommand(sub => sub.setName("list") .setDescription("List all feature flags") ) .addSubcommand(sub => sub.setName("create") .setDescription("Create a new feature flag") .addStringOption(opt => opt.setName("name") .setDescription("Name of the feature flag") .setRequired(true) ) .addStringOption(opt => opt.setName("description") .setDescription("Description of the feature flag") .setRequired(false) ) ) .addSubcommand(sub => sub.setName("delete") .setDescription("Delete a feature flag") .addStringOption(opt => opt.setName("name") .setDescription("Name of the feature flag") .setRequired(true) .setAutocomplete(true) ) ) .addSubcommand(sub => sub.setName("enable") .setDescription("Enable a feature flag") .addStringOption(opt => opt.setName("name") .setDescription("Name of the feature flag") .setRequired(true) .setAutocomplete(true) ) ) .addSubcommand(sub => sub.setName("disable") .setDescription("Disable a feature flag") .addStringOption(opt => opt.setName("name") .setDescription("Name of the feature flag") .setRequired(true) .setAutocomplete(true) ) ) .addSubcommand(sub => sub.setName("grant") .setDescription("Grant access to a feature flag") .addStringOption(opt => opt.setName("name") .setDescription("Name of the feature flag") .setRequired(true) .setAutocomplete(true) ) .addUserOption(opt => opt.setName("user") .setDescription("User to grant access to") .setRequired(false) ) .addRoleOption(opt => opt.setName("role") .setDescription("Role to grant access to") .setRequired(false) ) ) .addSubcommand(sub => sub.setName("revoke") .setDescription("Revoke access from a feature flag") .addIntegerOption(opt => opt.setName("id") .setDescription("Access record ID to revoke") .setRequired(true) ) ) .addSubcommand(sub => sub.setName("access") .setDescription("List access records for a feature flag") .addStringOption(opt => opt.setName("name") .setDescription("Name of the feature flag") .setRequired(true) .setAutocomplete(true) ) ), autocomplete: async (interaction) => { const focused = interaction.options.getFocused(true); if (focused.name === "name") { const flags = await featureFlagsService.listFlags(); const filtered = flags .filter(f => f.name.toLowerCase().includes(focused.value.toLowerCase())) .slice(0, 25); await interaction.respond( filtered.map(f => ({ name: `${f.name} (${f.enabled ? "enabled" : "disabled"})`, value: f.name })) ); } }, execute: async (interaction) => { await withCommandErrorHandling( interaction, async () => { const subcommand = interaction.options.getSubcommand(); switch (subcommand) { case "list": await handleList(interaction); break; case "create": await handleCreate(interaction); break; case "delete": await handleDelete(interaction); break; case "enable": await handleEnable(interaction); break; case "disable": await handleDisable(interaction); break; case "grant": await handleGrant(interaction); break; case "revoke": await handleRevoke(interaction); break; case "access": await handleAccess(interaction); break; } }, { ephemeral: true } ); }, }); async function handleList(interaction: ChatInputCommandInteraction) { const flags = await featureFlagsService.listFlags(); if (flags.length === 0) { await interaction.editReply({ embeds: [createBaseEmbed("Feature Flags", "No feature flags have been created yet.", Colors.Blue)] }); return; } const embed = createBaseEmbed("Feature Flags", undefined, Colors.Blue) .addFields( flags.map(f => ({ name: f.name, value: `${f.enabled ? "✅ Enabled" : "❌ Disabled"}\n${f.description || "*No description*"}`, inline: false, })) ); await interaction.editReply({ embeds: [embed] }); } async function handleCreate(interaction: ChatInputCommandInteraction) { const name = interaction.options.getString("name", true); const description = interaction.options.getString("description"); const flag = await featureFlagsService.createFlag(name, description ?? undefined); if (!flag) { await interaction.editReply({ embeds: [createErrorEmbed("Failed to create feature flag.")] }); return; } await interaction.editReply({ embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" created successfully. Use \`/featureflags enable\` to enable it.`)] }); } async function handleDelete(interaction: ChatInputCommandInteraction) { const name = interaction.options.getString("name", true); const flag = await featureFlagsService.deleteFlag(name); await interaction.editReply({ embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been deleted.`)] }); } async function handleEnable(interaction: ChatInputCommandInteraction) { const name = interaction.options.getString("name", true); const flag = await featureFlagsService.setFlagEnabled(name, true); await interaction.editReply({ embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been enabled.`)] }); } async function handleDisable(interaction: ChatInputCommandInteraction) { const name = interaction.options.getString("name", true); const flag = await featureFlagsService.setFlagEnabled(name, false); await interaction.editReply({ embeds: [createSuccessEmbed(`Feature flag "**${flag.name}**" has been disabled.`)] }); } async function handleGrant(interaction: ChatInputCommandInteraction) { const name = interaction.options.getString("name", true); const user = interaction.options.getUser("user"); const role = interaction.options.getRole("role"); if (!user && !role) { await interaction.editReply({ embeds: [createErrorEmbed("You must specify either a user or a role to grant access to.")] }); return; } const access = await featureFlagsService.grantAccess(name, { userId: user?.id, roleId: role?.id, guildId: interaction.guildId!, }); if (!access) { await interaction.editReply({ embeds: [createErrorEmbed("Failed to grant access.")] }); return; } let target: string; if (user) { target = userMention(user.id); } else if (role) { target = roleMention(role.id); } else { target = "Unknown"; } await interaction.editReply({ embeds: [createSuccessEmbed(`Access to "**${name}**" granted to ${target} (ID: ${access.id})`)] }); } async function handleRevoke(interaction: ChatInputCommandInteraction) { const id = interaction.options.getInteger("id", true); const access = await featureFlagsService.revokeAccess(id); await interaction.editReply({ embeds: [createSuccessEmbed(`Access record #${access.id} has been revoked.`)] }); } async function handleAccess(interaction: ChatInputCommandInteraction) { const name = interaction.options.getString("name", true); const accessRecords = await featureFlagsService.listAccess(name); if (accessRecords.length === 0) { await interaction.editReply({ embeds: [createBaseEmbed("Feature Flag Access", `No access records for "**${name}**".`, Colors.Blue)] }); return; } const fields = accessRecords.map(a => { let target = "Unknown"; if (a.userId) target = `User: ${userMention(a.userId.toString())}`; else if (a.roleId) target = `Role: ${roleMention(a.roleId.toString())}`; else if (a.guildId) target = `Guild: ${a.guildId.toString()}`; return { name: `ID: ${a.id}`, value: target, inline: true, }; }); const embed = createBaseEmbed(`Feature Flag Access: ${name}`, undefined, Colors.Blue) .addFields(fields); await interaction.editReply({ embeds: [embed] }); }