feat: Implement user enrollment interaction to assign a random class role and add new role configurations.
This commit is contained in:
@@ -21,6 +21,7 @@ export const classes = pgTable('classes', {
|
|||||||
id: bigint('id', { mode: 'bigint' }).primaryKey(),
|
id: bigint('id', { mode: 'bigint' }).primaryKey(),
|
||||||
name: varchar('name', { length: 255 }).unique().notNull(),
|
name: varchar('name', { length: 255 }).unique().notNull(),
|
||||||
balance: bigint('balance', { mode: 'bigint' }).default(0n),
|
balance: bigint('balance', { mode: 'bigint' }).default(0n),
|
||||||
|
roleId: varchar('role_id', { length: 255 }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Users
|
// 2. Users
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ const event: Event<Events.InteractionCreate> = {
|
|||||||
await import("@/modules/admin/item_wizard").then(m => m.handleItemWizardInteraction(interaction));
|
await import("@/modules/admin/item_wizard").then(m => m.handleItemWizardInteraction(interaction));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (interaction.customId === "enrollment" && interaction.isButton()) {
|
||||||
|
await import("@/modules/user/enrollment.interaction").then(m => m.handleEnrollmentInteraction(interaction));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.isAutocomplete()) {
|
if (interaction.isAutocomplete()) {
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export interface GameConfigType {
|
|||||||
currency: string;
|
currency: string;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
studentRole: string;
|
||||||
|
visitorRole: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial default config state
|
// Initial default config state
|
||||||
@@ -101,7 +103,10 @@ const configSchema = z.object({
|
|||||||
max: z.number(),
|
max: z.number(),
|
||||||
currency: z.string(),
|
currency: z.string(),
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
}),
|
||||||
|
studentRole: z.string(),
|
||||||
|
visitorRole: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export function reloadConfig() {
|
export function reloadConfig() {
|
||||||
@@ -132,6 +137,8 @@ export function reloadConfig() {
|
|||||||
};
|
};
|
||||||
config.commands = rawConfig.commands || {};
|
config.commands = rawConfig.commands || {};
|
||||||
config.lootdrop = rawConfig.lootdrop;
|
config.lootdrop = rawConfig.lootdrop;
|
||||||
|
config.studentRole = rawConfig.studentRole;
|
||||||
|
config.visitorRole = rawConfig.visitorRole;
|
||||||
|
|
||||||
console.log("🔄 Config reloaded from disk.");
|
console.log("🔄 Config reloaded from disk.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,5 +64,22 @@ export const classService = {
|
|||||||
return updatedClass;
|
return updatedClass;
|
||||||
};
|
};
|
||||||
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
||||||
|
},
|
||||||
|
|
||||||
|
createClass: async (data: typeof classes.$inferInsert, tx?: any) => {
|
||||||
|
const execute = async (txFn: any) => {
|
||||||
|
const [newClass] = await txFn.insert(classes)
|
||||||
|
.values(data)
|
||||||
|
.returning();
|
||||||
|
return newClass;
|
||||||
|
};
|
||||||
|
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteClass: async (id: bigint, tx?: any) => {
|
||||||
|
const execute = async (txFn: any) => {
|
||||||
|
await txFn.delete(classes).where(eq(classes.id, id));
|
||||||
|
};
|
||||||
|
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
94
src/modules/user/enrollment.interaction.ts
Normal file
94
src/modules/user/enrollment.interaction.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { ButtonInteraction, MessageFlags } from "discord.js";
|
||||||
|
import { config } from "@/lib/config";
|
||||||
|
import { createErrorEmbed } from "@/lib/embeds";
|
||||||
|
import { classService } from "@modules/class/class.service";
|
||||||
|
import { userService } from "@modules/user/user.service";
|
||||||
|
|
||||||
|
export async function handleEnrollmentInteraction(interaction: ButtonInteraction) {
|
||||||
|
if (!interaction.inCachedGuild()) {
|
||||||
|
await interaction.reply({ content: "This action can only be performed in a server.", flags: MessageFlags.Ephemeral });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { studentRole, visitorRole } = config;
|
||||||
|
|
||||||
|
if (!studentRole || !visitorRole) {
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [createErrorEmbed("No student or visitor role configured for enrollment.", "Configuration Error")],
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Ensure user exists in DB and check current enrollment status
|
||||||
|
const user = await userService.getOrCreateUser(interaction.user.id, interaction.user.username);
|
||||||
|
|
||||||
|
// Check DB enrollment
|
||||||
|
if (user.class) {
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [createErrorEmbed("You are already enrolled in a class.", "Enrollment Failed")],
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = interaction.member;
|
||||||
|
|
||||||
|
// Check Discord role enrollment (Double safety)
|
||||||
|
if (member.roles.cache.has(studentRole)) {
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [createErrorEmbed("You already have the student role.", "Enrollment Failed")],
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Get available classes
|
||||||
|
const allClasses = await classService.getAllClasses();
|
||||||
|
const validClasses = allClasses.filter(c => c.roleId);
|
||||||
|
|
||||||
|
if (validClasses.length === 0) {
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [createErrorEmbed("No classes with specified roles found in database.", "Configuration Error")],
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Pick random class
|
||||||
|
const selectedClass = validClasses[Math.floor(Math.random() * validClasses.length)]!;
|
||||||
|
const classRoleId = selectedClass.roleId!;
|
||||||
|
|
||||||
|
// Check if the role exists in the guild
|
||||||
|
const classRole = interaction.guild.roles.cache.get(classRoleId);
|
||||||
|
if (!classRole) {
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [createErrorEmbed(`The configured role ID \`${classRoleId}\` for class **${selectedClass.name}** does not exist in this server.`, "Configuration Error")],
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Perform Enrollment Actions
|
||||||
|
|
||||||
|
await member.roles.remove(visitorRole);
|
||||||
|
await member.roles.add(studentRole);
|
||||||
|
await member.roles.add(classRole);
|
||||||
|
|
||||||
|
// Persist to DB
|
||||||
|
await classService.assignClass(user.id.toString(), selectedClass.id);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
content: `🎉 You have been successfully enrolled! You are now a member of **${selectedClass.name}** and received the **${classRole.name}** role.`,
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Enrollment error:", error);
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [createErrorEmbed("An unexpected error occurred during enrollment. Please contact an administrator.", "System Error")],
|
||||||
|
flags: MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user