diff --git a/src/commands/admin/event/createevent.js b/src/commands/admin/event/createevent.js new file mode 100644 index 0000000..2887416 --- /dev/null +++ b/src/commands/admin/event/createevent.js @@ -0,0 +1,63 @@ +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { ConfigService } from '#services/configService.js'; +import logger from '#utils/logger.js'; +import { EventService } from '#services/eventService.js'; + +export const data = new SlashCommandBuilder() + .setName('create-event') + .setDescription('Create an event') + .addStringOption((option) => + option.setName('name').setDescription('The name of the event').setRequired(true) + ) + .addStringOption((option) => + option.setName('description').setDescription('The description of the event').setRequired(false) + ) + .addStringOption((option) => + option.setName('startdate').setDescription('The start date of the event').setRequired(false) + ) + .addStringOption((option) => + option.setName('enddate').setDescription('The end date of the event').setRequired(false) + ); + +/** + * @param {ChatInputCommandInteraction} interaction + */ +export async function execute(interaction) { + if (!interaction.member.permissions.has('Administrator')) { + const customRole = await ConfigService.getAdminRole(interaction.guildId); + if (customRole) { + const member = await interaction.guild.members.fetch(interaction.user.id); + if (!member.roles.cache.has(customRole)) { + await interaction.reply('You do not have permission to set the approval channel.'); + return; + } + } else { + await interaction.reply('You do not have permission to set the approval channel.'); + return; + } + } + + const name = interaction.options.getString('name'); + const description = interaction.options.getString('description'); + const startDate = interaction.options.getString('startdate'); + const endDate = interaction.options.getString('enddate'); + + // validate datetime format + const dateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/; + if (startDate && !dateFormat.test(startDate)) { + await interaction.reply('Invalid start date format. Please use ISO 8601 format.'); + return; + } + if (endDate && !dateFormat.test(endDate)) { + await interaction.reply('Invalid end date format. Please use ISO 8601 format.'); + return; + } + + try { + await EventService.createEvent(name, description, startDate, endDate); + await interaction.reply(`Event "${name}" created successfully!`); + logger.info(`Event "${name}" created successfully by ${interaction.user.tag}`); + } catch (error) { + await interaction.reply('An error occurred while creating the event. Please try again.'); + } +} diff --git a/src/commands/admin/event/editevent.js b/src/commands/admin/event/editevent.js new file mode 100644 index 0000000..14f344b --- /dev/null +++ b/src/commands/admin/event/editevent.js @@ -0,0 +1,108 @@ +import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { ConfigService } from '#services/configService.js'; +import logger from '#utils/logger.js'; +import { EventService } from '#services/eventService.js'; + +export const data = new SlashCommandBuilder() + .setName('edit-event') + .setDescription('Edit an event') + .addStringOption((option) => + option + .setName('event') + .setDescription('Select the event to edit') + .setRequired(true) + .setAutocomplete(true) + ) + .addStringOption((option) => + option.setName('name').setDescription('The name of the event').setRequired(false) + ) + .addStringOption((option) => + option.setName('description').setDescription('The description of the event').setRequired(false) + ) + .addStringOption((option) => + option.setName('startdate').setDescription('The start date of the event').setRequired(false) + ) + .addStringOption((option) => + option.setName('enddate').setDescription('The end date of the event').setRequired(false) + ) + .addStringOption((option) => + option + .setName('status') + .setDescription('The status of the event') + .setRequired(false) + .addChoices( + { name: 'Planning', value: 'PLANNING' }, + { name: 'Ongoing', value: 'ONGOING' }, + { name: 'Completed', value: 'COMPLETED' }, + { name: 'Canceled', value: 'CANCELLED' } + ) + ); + +/** + * @param {ChatInputCommandInteraction} interaction + */ +export async function execute(interaction) { + if (!interaction.member.permissions.has('Administrator')) { + const customRole = await ConfigService.getAdminRole(interaction.guildId); + if (customRole) { + const member = await interaction.guild.members.fetch(interaction.user.id); + if (!member.roles.cache.has(customRole)) { + await interaction.reply('You do not have permission to set the approval channel.'); + return; + } + } else { + await interaction.reply('You do not have permission to set the approval channel.'); + return; + } + } + + const eventId = interaction.options.getString('event'); + const name = interaction.options.getString('name'); + const description = interaction.options.getString('description'); + const startDate = interaction.options.getString('startdate'); + const endDate = interaction.options.getString('enddate'); + const status = interaction.options.getString('status'); + + if (!name && !description && !startDate && !endDate && !status) { + await interaction.reply('Please provide at least one field to edit.'); + return; + } + // validate datetime format + const dateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/; + if (startDate && !dateFormat.test(startDate)) { + await interaction.reply('Invalid start date format. Please use ISO 8601 format.'); + return; + } + if (endDate && !dateFormat.test(endDate)) { + await interaction.reply('Invalid end date format. Please use ISO 8601 format.'); + return; + } + + try { + const updatedEvent = await EventService.updateEvent( + eventId, + name, + description, + startDate, + endDate, + status + ); + await interaction.reply(`Event "${updatedEvent.name}" edited successfully!`); + logger.info( + `Event "${updatedEvent.name}" (${updatedEvent.id}) edited successfully by ${interaction.user.tag}` + ); + } catch (error) { + await interaction.reply('An error occurred while editing the event. Please try again.'); + } +} + +/** + * @param {ChatInputCommandInteraction} interaction + */ +export async function autocomplete(interaction) { + const focusedValue = interaction.options.getFocused(); + const events = await EventService.getEvents(); + const filtered = events.filter((event) => event.name.startsWith(focusedValue)); + const choices = filtered.map((event) => event).slice(0, 25); + await interaction.respond(choices.map((choice) => ({ name: choice.name, value: choice.id }))); +} diff --git a/src/events/interactionCreate.js b/src/events/interactionCreate.js index a573d34..42ab80c 100644 --- a/src/events/interactionCreate.js +++ b/src/events/interactionCreate.js @@ -1,20 +1,42 @@ -import { Events, MessageFlags } from 'discord.js'; +import { Events, MessageFlags, ChatInputCommandInteraction } from 'discord.js'; import logger from '#utils/logger.js'; export const name = Events.InteractionCreate; +/** + * @param {ChatInputCommandInteraction} interaction + */ export async function execute(interaction) { - if (!interaction.isChatInputCommand()) return; + if (interaction.isChatInputCommand()) { + const command = interaction.client.commands.get(interaction.commandName); + if (!command) return; - const command = interaction.client.commands.get(interaction.commandName); - if (!command) return; + try { + if (interaction.isAutocomplete()) { + await command.autocomplete(interaction); + } else { + await command.execute(interaction); + } + } catch (error) { + logger.error(error); + await interaction.reply({ + content: 'There was an error while executing this command!', + flags: MessageFlags.Ephemeral, + }); + } + } + + if (interaction.isAutocomplete()) { + const command = interaction.client.commands.get(interaction.commandName); + if (!command) return; - try { - await command.execute(interaction); - } catch (error) { - logger.error(error); - await interaction.reply({ - content: 'There was an error while executing this command!', - flags: MessageFlags.Ephemeral, - }); + try { + await command.autocomplete(interaction); + } catch (error) { + logger.error(error); + await interaction.reply({ + content: 'There was an error while executing this command!', + flags: MessageFlags.Ephemeral, + }); + } } } diff --git a/src/services/eventService.js b/src/services/eventService.js new file mode 100644 index 0000000..a7a3d66 --- /dev/null +++ b/src/services/eventService.js @@ -0,0 +1,98 @@ +import prisma from '#utils/prisma.js'; +import logger from '#utils/logger.js'; + +export class EventService { + /** + * Create a new event in the database + * @param {String} name - The name of the event + * @param {String} description - The description of the event + * @param {String} startDate - (Optional) The start date of the event + * @param {String} endDate - (Optional) The end date of the event + * @returns {Promise} + */ + static async createEvent(name, description, startDate = undefined, endDate = undefined) { + // Validate inputs + if (!name) { + throw new Error('Event name is required'); + } + // Create the event + try { + const eventData = { name, description }; + if (startDate) eventData.startDate = startDate; + eventData.startDate = new Date().toISOString(); + if (endDate !== undefined) eventData.endDate = endDate; + + await prisma.event.create({ + data: eventData, + }); + } catch (error) { + console.log(error); + logger.error('Error creating event', error.message); + throw error; + } + } + + /** + * Get all events + * @returns {Promise} An array of events + */ + static async getEvents() { + try { + const events = await prisma.event.findMany({ + orderBy: { + startDate: 'asc', + }, + }); + return events; + } catch (error) { + logger.error('Error getting events', error); + throw error; + } + } + + /** + * Delete an event by ID + * @param {String} eventId - The ID of the event + * @returns {Promise} + */ + static async deleteEvent(eventId) { + try { + await prisma.event.delete({ + where: { + id: eventId, + }, + }); + } catch (error) { + logger.error(`Error deleting event with ID ${eventId}:`, error); + throw error; + } + } + + /** + * Update an event by ID + * @param {String} eventId - The ID of the event + * @param {Object} data - The data to update + * @returns {Promise