Adds Deletion

This commit is contained in:
Michel Fedde 2025-03-29 20:15:50 +01:00
parent a0b668cb90
commit 0d9cf6a370
8 changed files with 174 additions and 35 deletions

View file

@ -0,0 +1,34 @@
import {DatabaseDefinition} from "../DatabaseDefinition";
export type DBGroup = {
id: number;
groupid: number;
key: string;
value: string;
}
const dbDefinition: DatabaseDefinition = {
name: "groups",
columns: [
{
name: "id",
type: "INTEGER",
autoIncrement: true,
primaryKey: true,
},
{
name: "groupid",
type: "VARCHAR(32)",
},
{
name: "key",
type: "VARCHAR(32)",
},
{
name: "value",
type: "VARCHAR(128)",
}
]
}
export default dbDefinition;

View file

@ -2,7 +2,7 @@ import {
AutocompleteInteraction, AutocompleteInteraction,
ChatInputCommandInteraction, ChatInputCommandInteraction,
CommandInteraction, CommandInteraction,
GuildMember, GuildMember, SlashCommandIntegerOption,
SlashCommandStringOption SlashCommandStringOption
} from "discord.js"; } from "discord.js";
import {Container} from "../../Container/Container"; import {Container} from "../../Container/Container";
@ -11,32 +11,32 @@ import {GroupModel} from "../../Models/GroupModel";
import {UserError} from "../UserError"; import {UserError} from "../UserError";
export class GroupSelection { export class GroupSelection {
public static createOptionSetup(): SlashCommandStringOption { public static createOptionSetup(): SlashCommandIntegerOption {
return new SlashCommandStringOption() return new SlashCommandIntegerOption()
.setName("group") .setName("group")
.setDescription("Defines the group you want to manage the playdates for") .setDescription("Defines the group you want to manage the playdates for")
.setRequired(true) .setRequired(true)
.setAutocomplete(true) .setAutocomplete(true)
} }
public static async handleAutocomplete(interaction: AutocompleteInteraction): Promise<void> { public static async handleAutocomplete(interaction: AutocompleteInteraction, onlyLeaders: boolean = false): Promise<void> {
const value = interaction.options.getFocused(); const value = interaction.options.getFocused();
const repo = Container.get<GroupRepository>(GroupRepository.name); const repo = Container.get<GroupRepository>(GroupRepository.name);
const groups = repo.findGroupsByMember(<GuildMember>interaction.member); let groups = repo.findGroupsByMember(<GuildMember>interaction.member, onlyLeaders);
await interaction.respond( await interaction.respond(
groups groups
.filter((group) => group.name.startsWith(value)) .filter((group) => group.name.startsWith(value))
.map((group) => ({name: group.name, value: group.name })) .map((group) => ({name: group.name, value: group.id }))
) )
} }
public static getGroup(interaction: CommandInteraction): GroupModel { public static getGroup(interaction: CommandInteraction): GroupModel {
const groupname = interaction.options.get("group"); const groupname = interaction.options.get("group", true);
if (!groupname) { if (!groupname) {
throw new UserError("No group name provided"); throw new UserError("No group name provided");
} }
const group = Container.get<GroupRepository>(GroupRepository.name).findGroupByName((groupname.value ?? '').toString()); const group = Container.get<GroupRepository>(GroupRepository.name).getById(<number>(groupname.value ?? 0));
if (!group) { if (!group) {
throw new UserError("No group found"); throw new UserError("No group found");
} }

View file

@ -3,15 +3,25 @@ import {
Interaction, Interaction,
CommandInteraction, CommandInteraction,
ChatInputCommandInteraction, ChatInputCommandInteraction,
MessageFlags, GuildMemberRoleManager, InteractionReplyOptions, GuildMember MessageFlags, GuildMemberRoleManager, InteractionReplyOptions, GuildMember, EmbedBuilder, AutocompleteInteraction
} from "discord.js"; } from "discord.js";
import {ChatInteractionCommand, Command} from "./Command"; import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
import {GroupModel} from "../../Models/GroupModel"; import {GroupModel} from "../../Models/GroupModel";
import {GroupRepository} from "../../Repositories/GroupRepository"; import {GroupRepository} from "../../Repositories/GroupRepository";
import {DatabaseConnection} from "../../Database/DatabaseConnection"; import {DatabaseConnection} from "../../Database/DatabaseConnection";
import {Container} from "../../Container/Container"; import {Container} from "../../Container/Container";
import {GroupSelection} from "../CommandPartials/GroupSelection";
import {UserError} from "../UserError";
import {ArrayUtils} from "../../Utilities/ArrayUtils";
export class GroupCommand implements Command, ChatInteractionCommand, AutocompleteCommand {
private static GOODBYE_MESSAGES: string[] = [
'Sad to see you go.',
'May your next adventure be fruitful.',
'I hope, I served you well.',
'I wish you, good luck on your next adventures.',
]
export class GroupCommand implements Command, ChatInteractionCommand {
definition(): SlashCommandBuilder { definition(): SlashCommandBuilder {
// @ts-ignore // @ts-ignore
return new SlashCommandBuilder() return new SlashCommandBuilder()
@ -35,8 +45,17 @@ export class GroupCommand implements Command, ChatInteractionCommand {
listCommand listCommand
.setName("list") .setName("list")
.setDescription("Displays the groups you are apart of.") .setDescription("Displays the groups you are apart of.")
)
.addSubcommand(command => command
.setName('config')
.setDescription("Starts the config manager for the group.")
.addIntegerOption(GroupSelection.createOptionSetup())
)
.addSubcommand(command => command
.setName("remove")
.setDescription("Deletes a group you are the leader for.")
.addIntegerOption(GroupSelection.createOptionSetup())
); );
} }
execute(interaction: ChatInputCommandInteraction): Promise<void> { execute(interaction: ChatInputCommandInteraction): Promise<void> {
switch (interaction.options.getSubcommand()) { switch (interaction.options.getSubcommand()) {
@ -46,6 +65,11 @@ export class GroupCommand implements Command, ChatInteractionCommand {
case "list": case "list":
this.list(interaction); this.list(interaction);
break; break;
case "remove":
this.remove(interaction);
break;
case "config":
this.runConfigurator(interaction);
default: default:
throw new Error("Unsupported command"); throw new Error("Unsupported command");
} }
@ -79,22 +103,65 @@ export class GroupCommand implements Command, ChatInteractionCommand {
const repo = Container.get<GroupRepository>(GroupRepository.name); const repo = Container.get<GroupRepository>(GroupRepository.name);
const groups = repo.findGroupsByMember(<GuildMember>interaction.member); const groups = repo.findGroupsByMember(<GuildMember>interaction.member);
const reply: InteractionReplyOptions = { const embed = new EmbedBuilder()
embeds: [ .setTitle("Your groups on this server:")
{ .setFields(
title: "Your groups on this server:", groups.map(group => {
fields: groups.map((group) => {
return { return {
name: group.name, name: group.name,
value: "" value: `
Role: <@&${group.role.roleid}>
`
} }
}) })
} )
const reply: InteractionReplyOptions = {
embeds: [
embed
], ],
allowedMentions: { roles: [] },
flags: MessageFlags.Ephemeral flags: MessageFlags.Ephemeral
} }
interaction.reply(reply); interaction.reply(reply);
} }
private async remove(interaction: ChatInputCommandInteraction) {
const group = GroupSelection.getGroup(interaction);
const repo = Container.get<GroupRepository>(GroupRepository.name);
if (group.leader.memberid != interaction.member?.user.id) {
throw new UserError("Can't remove group. You are not the leader.");
}
repo.deleteGroup(group);
const embed = new EmbedBuilder()
.setTitle("Group deleted.")
.setDescription(
`:x: Deleted \`${group.name}\`. ${ArrayUtils.chooseRandom(GroupCommand.GOODBYE_MESSAGES)}`
)
await interaction.reply({
embeds: [
embed
],
flags: MessageFlags.Ephemeral,
})
}
async handleAutocomplete(interaction: AutocompleteInteraction): Promise<void> {
const option = interaction.options.getFocused(true);
if (option.name == "group") {
await GroupSelection.handleAutocomplete(interaction, true);
return;
}
}
private runConfigurator(interaction: ChatInputCommandInteraction) {
const group = GroupSelection.getGroup(interaction);
}
} }

View file

@ -31,7 +31,7 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
.addSubcommand((subcommand) => subcommand .addSubcommand((subcommand) => subcommand
.setName("create") .setName("create")
.setDescription("Creates a new playdate") .setDescription("Creates a new playdate")
.addStringOption(GroupSelection.createOptionSetup()) .addIntegerOption(GroupSelection.createOptionSetup())
.addStringOption((option) => option .addStringOption((option) => option
.setName("from") .setName("from")
.setDescription("Defines the start date & time. Format: YYYY-MM-DD HH:mm") .setDescription("Defines the start date & time. Format: YYYY-MM-DD HH:mm")
@ -48,12 +48,12 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
.addSubcommand((subcommand) => subcommand .addSubcommand((subcommand) => subcommand
.setName("list") .setName("list")
.setDescription("Lists all playdates") .setDescription("Lists all playdates")
.addStringOption(GroupSelection.createOptionSetup()) .addIntegerOption(GroupSelection.createOptionSetup())
) )
.addSubcommand((subcommand) => subcommand .addSubcommand((subcommand) => subcommand
.setName("remove") .setName("remove")
.setDescription("Removes a playdate") .setDescription("Removes a playdate")
.addStringOption(GroupSelection.createOptionSetup()) .addIntegerOption(GroupSelection.createOptionSetup())
.addIntegerOption((option) => option .addIntegerOption((option) => option
.setName("playdate") .setName("playdate")
.setDescription("Selects a playdate") .setDescription("Selects a playdate")
@ -179,8 +179,6 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
throw new UserError("No playdate found"); throw new UserError("No playdate found");
} }
console.log(selected, group);
if (selected.group?.id != group.id) { if (selected.group?.id != group.id) {
throw new UserError("No playdate found"); throw new UserError("No playdate found");
} }

View file

@ -0,0 +1,8 @@
import {Model} from "./Model";
import {GroupModel} from "./GroupModel";
export interface GroupConfigurationModel extends Model {
group: GroupModel;
key: string;
value: string;
}

View file

@ -4,6 +4,8 @@ import Groups, {DBGroup} from "../Database/tables/Groups";
import {DatabaseConnection} from "../Database/DatabaseConnection"; import {DatabaseConnection} from "../Database/DatabaseConnection";
import {CacheType, CacheTypeReducer, Guild, GuildMember, GuildMemberRoleManager} from "discord.js"; import {CacheType, CacheTypeReducer, Guild, GuildMember, GuildMemberRoleManager} from "discord.js";
import {Nullable} from "../types/Nullable"; import {Nullable} from "../types/Nullable";
import {PlaydateRepository} from "./PlaydateRepository";
import {Container} from "../Container/Container";
export class GroupRepository extends Repository<GroupModel, DBGroup> { export class GroupRepository extends Repository<GroupModel, DBGroup> {
@ -43,12 +45,31 @@ export class GroupRepository extends Repository<GroupModel, DBGroup> {
return dbResult.map((result) => this.convertToModelType(result)); return dbResult.map((result) => this.convertToModelType(result));
} }
public findGroupsByMember(member: GuildMember) { public findGroupsByMember(member: GuildMember, onlyLeader: boolean = false) {
if (!member) { if (!member) {
throw new Error("Can't find member for guild: none given"); throw new Error("Can't find member for guild: none given");
} }
return this.findGroupsByRoles(member.guild.id, [...member.roles.cache.keys()]) const groups = this.findGroupsByRoles(member.guild.id, [...member.roles.cache.keys()])
if (!onlyLeader) {
return groups;
}
return groups.filter((group: GroupModel) => {
return group.leader.memberid === member.id;
})
}
public deleteGroup(group: GroupModel): void {
this.delete(group.id);
debugger
const repo = Container.get<PlaydateRepository>(PlaydateRepository.name);
const playdates = repo.findFromGroup(group, true)
playdates.forEach((playdate) => {
repo.delete(playdate.id);
})
} }
protected convertToModelType(intermediateModel: DBGroup | undefined): GroupModel { protected convertToModelType(intermediateModel: DBGroup | undefined): GroupModel {

View file

@ -5,7 +5,6 @@ import {DatabaseConnection} from "../Database/DatabaseConnection";
import {GroupRepository} from "./GroupRepository"; import {GroupRepository} from "./GroupRepository";
import {GroupModel} from "../Models/GroupModel"; import {GroupModel} from "../Models/GroupModel";
import {Nullable} from "../types/Nullable"; import {Nullable} from "../types/Nullable";
import playdate from "../Database/tables/Playdate";
export class PlaydateRepository extends Repository<PlaydateModel, DBPlaydate> { export class PlaydateRepository extends Repository<PlaydateModel, DBPlaydate> {
@ -19,16 +18,22 @@ export class PlaydateRepository extends Repository<PlaydateModel, DBPlaydate> {
); );
} }
findFromGroup(group: GroupModel) { findFromGroup(group: GroupModel, all = false) {
let sql = `SELECT * FROM ${this.schema.name} WHERE groupid = ?`;
const params = [group.id];
if (!all) {
sql += " AND time_from > ?"
params.push(new Date().getTime())
}
const finds = this.database.fetchAll<number, DBPlaydate>( const finds = this.database.fetchAll<number, DBPlaydate>(
`SELECT * FROM ${this.schema.name} WHERE groupid = ? AND time_from > ?`, sql,
group.id, ...params
new Date().getTime()
); );
return finds.map((playdate) => this.convertToModelType(playdate, group)); return finds.map((playdate) => this.convertToModelType(playdate, group));
} }
protected convertToModelType(intermediateModel: DBPlaydate | undefined, fixedGroup: Nullable<GroupModel> = null): PlaydateModel { protected convertToModelType(intermediateModel: DBPlaydate | undefined, fixedGroup: Nullable<GroupModel> = null): PlaydateModel {
if (!intermediateModel) { if (!intermediateModel) {
throw new Error("Unable to convert the playdate model"); throw new Error("Unable to convert the playdate model");

View file

@ -0,0 +1,6 @@
export class ArrayUtils {
public static chooseRandom<T>(array: Array<T>):T {
const index = Math.floor(Math.random() * array.length);
return array[index];
}
}