'Create a slash command choice for every mongoDB entry with the ID

Basically what I'm trying to do is create a choice within my SlashCommanderBuilder for every entry of my mongoDB database with the id which is returned. The entries of my database look like the following:

id:"test"
title:"Test Embed"
description:"This is a test embed."
link:"test"

So in my code below, I need to retrieve all entries from the database (which must be asynchronous) and then use each entry in a for loop to set the choice. The issue I'm having is my SlashCommandBuilder isn't asynchronous, and surrounding the .find inside an asynchronous function renders the data unusable outside the scope. I fear the structure of how I'm storing these commands isn't playing nicely with what I'm trying to achieve.

    const { SlashCommandBuilder } = require("@discordjs/builders");
    const { MessageEmbed, MessageActionRow, MessageButton } = require('discord.js');
    const { ROLE } = require('../../../config/roles.js');
    const config = require('../../../config/config.json')
    const channel = require('../../../config/channel_ids.json')
    const addfeatureSchema = require('../../database/models/addfeatureSchema.js');

    let thing;
    async function getAThing() {
          thing = await addfeatureSchema.find();
          return thing;
    }

    // Set Role.<Role> if this command should be restricted to a specific role. Refer to /config/roles.js for the enums
    const allowedRoles = [ROLE.Owner];

      const data = new SlashCommandBuilder()
      .setName("editfeature")
      .setDescription('Edit a feature embed')
      .addStringOption(option => {
        option.setName('id')
          .setDescription('ID')
          .setRequired(true)
          getAThing().then(() => {
            for (const record of thing) {
              option.addChoice(record.id, record.id)
            }
          })
          return option;
      })
      
    async function run(interaction) {
      const client          = interaction.client;

    }
        
    module.exports = {
      allowedRoles,
      data,
      run
    };

For further reference, I've included how the command files are handled in the index.js.


    async function refreshCmds() {
      fs.readdirSync(`${config.command_dir}`).forEach(group => {
        const commandFiles = fs.readdirSync(`${config.command_dir}${group}`).filter(file => file.endsWith('.js'));
      
        for (const file of commandFiles) {
            const command = require(`${config.command_dir}${group}/${file}`)
            commands.push(command.data.toJSON());
            client_commands.set(command.data.name, command);
            console.log(`Loaded command file: ${group} -> ${file}`)
        }
      });
      try {
        console.log('Started refreshing application (/) commands.');
        await rest.put(Routes.applicationGuildCommands(client_id, guild_id), { body: commands });
        console.log('Successfully reloaded application (/) commands.');
      } catch (error) {
        console.error(error);
      }
    }

    async function handleCommand(interaction) {
        if (!interaction.isCommand() && !interaction.isButton()) return;

        const command = client_commands.get(interaction.commandName);
        if (!command) return;

        if (command.allowedRoles && command.allowedRoles.length != 0) {
          if (!interaction.member.roles.cache.hasAny(...command.allowedRoles)) {
            const Error1 = new MessageEmbed()
              .setColor('RED')
              .setDescription(`You do not have permission to run this command.`)
            await interaction.deferReply({ephemeral: true});
            await interaction.editReply({embeds: [Error1], ephemeral: true})
            return;
          }
        }

        try {
            await command.run(interaction);
        } catch (error) {
            console.error(error);
            // await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
            const executionErrorEmbed = new MessageEmbed()
              .setColor(config.color)
              .setTitle('Uh oh...')
              .setDescription('There was an error while executing this command. **Please contact <@279334901000699904>**.');
            await interaction.reply({content: ' ', embeds: [executionErrorEmbed], ephemeral: true})
        }
      };


    client.once("ready", () => {
        refreshCmds();
        client.on('interactionCreate', async (interaction) => {
          handleCommand(interaction);
        });
        console.log("Online");
    });

Any help is greatly appreciated and I'm happy to alter my file structure accordingly.



Solution 1:[1]

Not a full answer but here is my suggestion :

thing is your collection of option records and addfeatureSchema.find() will return a promise. So instead of doing the asynchronous call in the SlashCommandBuilder why not declare your new slash command inside the promise then() method where you can access all your records ? You could then use the addChoices() method instead of a loop of addChoice() calls to add all the choices at once with an array.

something like

addfeatureSchema.find().then(thing => {
    
    // prepare you choices array before
    const choicesArray = [];
    for (const record of thing) {
        choicesArray.push([record.id, record.id]);
    }

    const data = new SlashCommandBuilder()
    .setName("editfeature")
    .setDescription('Edit a feature embed')
    .addStringOption(option => {
        option.setName('id')
        .setDescription('ID')
        .setRequired(true)
        .addChoices(choicesArray)
    });

}).catch(error => {
    console.log(error);
});

It might screw the export since it's just moving the scope issue from one point to another, but I hope that's helpful.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 maximob