diff --git a/.github/workflows/releases.yaml b/.github/workflows/releases.yaml index 7a017cb..4d3da5a 100644 --- a/.github/workflows/releases.yaml +++ b/.github/workflows/releases.yaml @@ -30,4 +30,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} with: - files: build/libs/ydw.jar \ No newline at end of file + files: build/libs/programming_bot.jar \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4503571..83b8c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ bin/ .DS_Store codecov *.log +gradle.properties diff --git a/README.md b/README.md index bf625a7..47fdec5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Java CI with Gradle](https://github.com/Programming-Org/Programming/actions/workflows/gradle.yml/badge.svg)](https://github.com/YusufsDiscordbot/Yusuf-s-Moderation-Bot/actions/workflows/gradle.yml) +[![Java CI with Gradle](https://github.com/Programming-Org/Programming/actions/workflows/gradle.yml/badge.svg)](https://github.com/Programming-Org/Programming/actions/workflows/gradle.yml) [![codecov](https://codecov.io/gh/Programming-Org/Programming/branch/main/graph/badge.svg?token=M1KBWF0CDY)](https://codecov.io/gh/Programming-Org/Programming) # Welcome to the Programming Server's Discord bot! @@ -33,11 +33,29 @@ Once you have added the bot to your server, you will need to create a .env file TOKEN= GUILD_ID= OWNER_ID= -//for the excutor default is 1; +//for the excutor default is 1. CORE_POOL_SIZE= +DATABASE_NAME=programming_bot +DB_USER= +DB_PASSWORD= +DB_URL= +PORT_NUMBER= +SERVER_NAME= ``` ### Part 3 +To get started with postgres you will need to install it using homebrew or any other package manager. + +To install it with homebrew run the following command: +```brew install postgresql``` + +For linux see the [postgresql download](https://www.postgresql.org/download/linux/) + +To start the server run the following command: +```brew servers start postgresql``` + + +### Part 4 In order to make a command for the bot, you will need to create an issue and wait to see if the command is needed. diff --git a/build.gradle b/build.gradle index 07e5df2..a48b571 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,18 @@ +import nu.studer.gradle.jooq.JooqEdition + +buildscript { + dependencies { + classpath 'org.postgresql:postgresql:42.4.0' + } +} + plugins { id 'java' id "com.diffplug.spotless" version "6.6.1" id "application" + id "nu.studer.jooq" version "6.0.1" } - group 'io.github.org.programming' version '1.0-SNAPSHOT' @@ -24,16 +32,21 @@ dependencies { implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.11' //config implementation group: 'io.github.yusufsdiscordbot', name: 'config', version: '1.0.4' - //Reflection - implementation group: 'org.reflections', name: 'reflections', version: '0.10.2' //json implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.13.3' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.13.3' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.3' - //MongoDB - implementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.12.11' + //database + implementation group: 'com.zaxxer', name: 'HikariCP', version: '5.0.0' + implementation group: 'org.postgresql', name: 'postgresql', version: '42.4.0' + //jooq + implementation group: 'org.jooq', name: 'jooq', version: '3.15.5' + implementation group: 'org.jooq', name: 'jooq-codegen', version: '3.16.6' + implementation group: 'org.jooq', name: 'jooq-meta', version: '3.16.6' //test testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' + //jooq + jooqGenerator "org.postgresql:postgresql:42.4.0" } apply plugin: 'jacoco' // code coverage reports @@ -77,8 +90,10 @@ compileJava { spotless { java { - // Excludes build folder since it contains generated java classes. + // Excludes build folder since it may contain compiled classes targetExclude("build/**") + //No need to do spotless on generated code + targetExclude("src/main/jooq/**") eclipse('4.21.0').configFile("${rootProject.rootDir}/meta/formatting/google-style-eclipse.xml") } } @@ -131,4 +146,70 @@ task downloadDependencies { } } +def props = new Properties() +//if the file exists, load the properties +if (file(".env").exists()) { + file(".env").withInputStream { props.load(it) } +} + + +//create the build directory so no errors happen with the workflow +task createBuildDir { + doLast { + logger.info("===== Creating build directory =====") + file("build").mkdirs() + logger.info("===== Build directory created ======") + } + + mustRunAfter(project.tasks.findByPath('clean')) +} + + +jooq { + version = '3.15.2' + edition = JooqEdition.OSS + configurations { + main { + generateSchemaSourceOnCompilation = false + generationTool { + logging = org.jooq.meta.jaxb.Logging.WARN + jdbc { + driver = 'org.postgresql.Driver' + url = props.getProperty("DB_URL", "null") + user = props.getProperty("DB_USER", "null") + password = props.getProperty("DB_PASSWORD", "null") + } + generator { + name = 'org.jooq.codegen.JavaGenerator' + database { + name = 'org.jooq.meta.postgres.PostgresDatabase' + inputSchema = 'public' + includes = '.*' + excludes = '' + + forcedTypes { + forcedType { + name = 'INSTANT' + includeTypes = 'TIMESTAMP' + } + } + } + generate { + records = true + immutablePojos = true + fluentSetters = true + + } + target { + packageName = 'io.github.org.programming.jooq' + directory = "src/main/jooq" + } + strategy.name = "org.jooq.codegen.DefaultGeneratorStrategy" + } + } + } + } +} + + check.dependsOn jacocoTestReport diff --git a/src/main/java/io/github/org/programming/backend/extension/MessageCommandExtender.java b/src/main/java/io/github/org/programming/backend/extension/MessageCommandExtender.java index 7bd2e84..0bb438b 100644 --- a/src/main/java/io/github/org/programming/backend/extension/MessageCommandExtender.java +++ b/src/main/java/io/github/org/programming/backend/extension/MessageCommandExtender.java @@ -1,21 +1,15 @@ package io.github.org.programming.backend.extension; import io.github.org.programming.backend.builder.message.MessageCommand; -import io.github.org.programming.backend.builder.slash.SlashCommand; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.build.CommandData; import javax.annotation.Nonnull; -public abstract class MessageCommandExtender extends MessageCommand { - - public MessageCommandExtender(CommandData commandData) { - super(commandData); - } +public abstract class MessageCommandExtender { public abstract void onMessageContextInteraction(@Nonnull MessageContextInteractionEvent event); @@ -35,5 +29,5 @@ public void onSelectMenu(@Nonnull SelectMenuInteractionEvent event) { } - protected abstract SlashCommand build(); + public abstract MessageCommand build(); } diff --git a/src/main/java/io/github/org/programming/backend/extension/SlashCommandExtender.java b/src/main/java/io/github/org/programming/backend/extension/SlashCommandExtender.java index f753e9c..53b5c25 100644 --- a/src/main/java/io/github/org/programming/backend/extension/SlashCommandExtender.java +++ b/src/main/java/io/github/org/programming/backend/extension/SlashCommandExtender.java @@ -6,16 +6,11 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; -public abstract class SlashCommandExtender extends SlashCommand { - - protected SlashCommandExtender(SlashCommandData commandData) { - super(commandData); - } +public abstract class SlashCommandExtender { // This method is called when the command is executed. public abstract void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event); @@ -36,5 +31,5 @@ public void onSelectMenu(@Nonnull SelectMenuInteractionEvent event) { } - protected abstract SlashCommand build(); + public abstract SlashCommand build(); } diff --git a/src/main/java/io/github/org/programming/backend/extension/UserCommandExtender.java b/src/main/java/io/github/org/programming/backend/extension/UserCommandExtender.java index eae6014..2150c34 100644 --- a/src/main/java/io/github/org/programming/backend/extension/UserCommandExtender.java +++ b/src/main/java/io/github/org/programming/backend/extension/UserCommandExtender.java @@ -6,15 +6,11 @@ import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.build.CommandData; import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; -public abstract class UserCommandExtender extends UserCommand { - public UserCommandExtender(CommandData commandData) { - super(commandData); - } +public abstract class UserCommandExtender { public abstract void onUserContextInteraction(@NotNull UserContextInteractionEvent event); @@ -34,5 +30,5 @@ public void onSelectMenu(@Nonnull SelectMenuInteractionEvent event) { } - protected abstract UserCommand build(); + public abstract UserCommand build(); } diff --git a/src/main/java/io/github/org/programming/backend/handler/BaseHandler.java b/src/main/java/io/github/org/programming/backend/handler/BaseHandler.java index 225e99b..aa77404 100644 --- a/src/main/java/io/github/org/programming/backend/handler/BaseHandler.java +++ b/src/main/java/io/github/org/programming/backend/handler/BaseHandler.java @@ -2,6 +2,8 @@ import net.dv8tion.jda.api.hooks.ListenerAdapter; +import java.util.Objects; + public abstract class BaseHandler extends ListenerAdapter { /** * @@ -10,4 +12,9 @@ public abstract class BaseHandler extends ListenerAdapter { protected abstract long botOwnerId(); + public static void checkIfBuildIsNull(Object build) { + if (Objects.isNull(build)) { + throw new IllegalArgumentException("Build is null"); + } + } } diff --git a/src/main/java/io/github/org/programming/backend/handler/MessageCommandHandler.java b/src/main/java/io/github/org/programming/backend/handler/MessageCommandHandler.java index 25d54ac..9efa853 100644 --- a/src/main/java/io/github/org/programming/backend/handler/MessageCommandHandler.java +++ b/src/main/java/io/github/org/programming/backend/handler/MessageCommandHandler.java @@ -44,18 +44,20 @@ protected MessageCommandHandler(@NotNull JDA jda, @NotNull Guild guild) { * The Command class is an interface class which contains all the need methods for the * making of the command.
*
- * The boolean {@link MessageCommandExtender#isGuildOnly()} ()} ()} is used to determine + * The boolean {@link MessageCommandExtender#build#isGuildOnly()} is used to determine * whether the command should be global or guild only. determines whether the command * should be Global or Guild only. */ @SuppressWarnings("ResultOfMethodCallIgnored") private void addMessageCommand(@NotNull MessageCommandExtender command) { - jda.addEventListener(command); - messageCommand.put(command.getCommandData().getName(), command); - if (command.isGuildOnly()) { - guildCommandsData.addCommands(command.getCommandData()); + + BaseHandler.checkIfBuildIsNull(command.build()); + + messageCommand.put(command.build().getCommandData().getName(), command); + if (command.build().isGuildOnly()) { + guildCommandsData.addCommands(command.build().getCommandData()); } else { - globalCommandsData.addCommands(command.getCommandData()); + globalCommandsData.addCommands(command.build().getCommandData()); } } @@ -88,22 +90,27 @@ private void onFinishedRegistration() { public void onMessageContextInteraction(@Nonnull MessageContextInteractionEvent event) { final MessageCommandExtender cmd = messageCommand.get(event.getName()); - if (cmd.isOwnerOnly() && event.getUser().getIdLong() != botOwnerId()) { + if (cmd.build().isOwnerOnly() && event.getUser().getIdLong() != botOwnerId()) { event.reply("You do not have permission to use this command.") .setEphemeral(true) .queue(); return; } - if (Objects.requireNonNull(event.getMember()).hasPermission(cmd.getUserPerms()) - || Objects.requireNonNull(event.getGuild()) - .getSelfMember() - .hasPermission(cmd.getBotPerms())) { - cmd.onMessageContextInteraction(event); - } else { + if (cmd.build().getUserPerms() != null + && event.getMember().hasPermission(cmd.build().getUserPerms())) { event.reply("You do not have permission to use this command.") .setEphemeral(true) .queue(); + } else { + cmd.onMessageContextInteraction(event); + } + + if (cmd.build().getBotPerms() != null + && event.getGuild().getSelfMember().hasPermission(cmd.build().getBotPerms())) { + event.reply("I do not have permission to use this command.").setEphemeral(true).queue(); + } else { + cmd.onMessageContextInteraction(event); } } diff --git a/src/main/java/io/github/org/programming/backend/handler/SlashCommandHandler.java b/src/main/java/io/github/org/programming/backend/handler/SlashCommandHandler.java index f22c580..384b3ba 100644 --- a/src/main/java/io/github/org/programming/backend/handler/SlashCommandHandler.java +++ b/src/main/java/io/github/org/programming/backend/handler/SlashCommandHandler.java @@ -66,18 +66,20 @@ protected SlashCommandHandler(@NotNull JDA jda, @NotNull Guild guild) { * The Command class is an interface class which contains all the need methods for the * making of the command.
*
- * The boolean {@link SlashCommandExtender#isGuildOnly()} ()} ()} is used to determine + * The boolean {@link SlashCommandExtender#build()#isGuildOnly()} is used to determine * whether the command should be global or guild only. determines whether the command * should be Global or Guild only. */ @SuppressWarnings("ResultOfMethodCallIgnored") private void addSlashCommand(@NotNull SlashCommandExtender command) { - jda.addEventListener(command); - slashCommand.put(command.getSlashCommandData().getName(), command); - if (command.isGuildOnly()) { - guildCommandsData.addCommands(command.getSlashCommandData()); + + BaseHandler.checkIfBuildIsNull(command.build()); + + slashCommand.put(command.build().getSlashCommandData().getName(), command); + if (command.build().isGuildOnly()) { + guildCommandsData.addCommands(command.build().getSlashCommandData()); } else { - globalCommandsData.addCommands(command.getSlashCommandData()); + globalCommandsData.addCommands(command.build().getSlashCommandData()); } } @@ -110,22 +112,27 @@ private void onFinishedRegistration() { public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) { final SlashCommandExtender cmd = slashCommand.get(event.getName()); - if (cmd.isOwnerOnly() && event.getUser().getIdLong() != botOwnerId()) { + if (cmd.build().isOwnerOnly() && event.getUser().getIdLong() != botOwnerId()) { event.reply("You do not have permission to use this command.") .setEphemeral(true) .queue(); return; } - if (Objects.requireNonNull(event.getMember()).hasPermission(cmd.getUserPerms()) - || Objects.requireNonNull(event.getGuild()) - .getSelfMember() - .hasPermission(cmd.getBotPerms())) { - cmd.onSlashCommandInteraction(event); - } else { + if (cmd.build().getUserPerms() != null + && event.getMember().hasPermission(cmd.build().getUserPerms())) { event.reply("You do not have permission to use this command.") .setEphemeral(true) .queue(); + } else { + cmd.onSlashCommandInteraction(event); + } + + if (cmd.build().getBotPerms() != null + && event.getGuild().getSelfMember().hasPermission(cmd.build().getBotPerms())) { + event.reply("I do not have permission to use this command.").setEphemeral(true).queue(); + } else { + cmd.onSlashCommandInteraction(event); } } diff --git a/src/main/java/io/github/org/programming/backend/handler/UserCommandHandler.java b/src/main/java/io/github/org/programming/backend/handler/UserCommandHandler.java index fea6437..5fe293a 100644 --- a/src/main/java/io/github/org/programming/backend/handler/UserCommandHandler.java +++ b/src/main/java/io/github/org/programming/backend/handler/UserCommandHandler.java @@ -45,18 +45,20 @@ protected UserCommandHandler(@NotNull JDA jda, @NotNull Guild guild) { * The Command class is an interface class which contains all the need methods for the * making of the command.
*
- * The boolean {@link UserCommandExtender#isGuildOnly()} ()} ()} is used to determine + * The boolean {@link UserCommandExtender#build#isGuildOnly()} is used to determine * whether the command should be global or guild only. determines whether the command * should be Global or Guild only. */ @SuppressWarnings("ResultOfMethodCallIgnored") private void addUserCommand(@NotNull UserCommandExtender command) { - jda.addEventListener(command); - userCommand.put(command.getCommandData().getName(), command); - if (command.isGuildOnly()) { - guildCommandsData.addCommands(command.getCommandData()); + + BaseHandler.checkIfBuildIsNull(command.build()); + + userCommand.put(command.build().getCommandData().getName(), command); + if (command.build().isGuildOnly()) { + guildCommandsData.addCommands(command.build().getCommandData()); } else { - globalCommandsData.addCommands(command.getCommandData()); + globalCommandsData.addCommands(command.build().getCommandData()); } } @@ -89,22 +91,27 @@ private void onFinishedRegistration() { public void onUserContextInteraction(@NotNull UserContextInteractionEvent event) { final UserCommandExtender cmd = userCommand.get(event.getName()); - if (cmd.isOwnerOnly() && event.getUser().getIdLong() != botOwnerId()) { + if (cmd.build().isOwnerOnly() && event.getUser().getIdLong() != botOwnerId()) { event.reply("You do not have permission to use this command.") .setEphemeral(true) .queue(); return; } - if (Objects.requireNonNull(event.getMember()).hasPermission(cmd.getUserPerms()) - || Objects.requireNonNull(event.getGuild()) - .getSelfMember() - .hasPermission(cmd.getBotPerms())) { - cmd.onUserContextInteraction(event); - } else { + if (cmd.build().getUserPerms() != null + && event.getMember().hasPermission(cmd.build().getUserPerms())) { event.reply("You do not have permission to use this command.") .setEphemeral(true) .queue(); + } else { + cmd.onUserContextInteraction(event); + } + + if (cmd.build().getBotPerms() != null + && event.getGuild().getSelfMember().hasPermission(cmd.build().getBotPerms())) { + event.reply("I do not have permission to use this command.").setEphemeral(true).queue(); + } else { + cmd.onUserContextInteraction(event); } } diff --git a/src/main/java/io/github/org/programming/bot/ProgrammingBot.java b/src/main/java/io/github/org/programming/bot/ProgrammingBot.java index 6903db2..80640db 100644 --- a/src/main/java/io/github/org/programming/bot/ProgrammingBot.java +++ b/src/main/java/io/github/org/programming/bot/ProgrammingBot.java @@ -1,6 +1,7 @@ package io.github.org.programming.bot; -import io.github.org.programming.config.BotConfig; +import io.github.org.programming.bot.config.BotConfig; +import io.github.org.programming.database.Database; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.OnlineStatus; @@ -11,6 +12,9 @@ import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.utils.cache.CacheFlag; import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; +import org.jooq.SQLDialect; +import org.jooq.impl.DSL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,14 +25,17 @@ public class ProgrammingBot extends ListenerAdapter { private final Logger logger = LoggerFactory.getLogger(ProgrammingBot.class); - private static ProgrammingBot instance; - private final ExecutorService executor = Executors.newCachedThreadPool(); private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(BotConfig.getCorePoolSize()); + private static DSLContext context; + public ProgrammingBot(String[] args) throws Exception { + + onDatabase(); + JDA jda = JDABuilder .createDefault(BotConfig.getToken(), GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_VOICE_STATES, GatewayIntent.GUILD_MESSAGE_REACTIONS, @@ -39,21 +46,44 @@ public ProgrammingBot(String[] args) throws Exception { .setStatus(OnlineStatus.ONLINE) .build(); + Guild guild = jda.awaitReady().getGuildById(BotConfig.getGuildId()); jda.awaitReady().addEventListener(new SlashCommandReg(jda, guild), this); } - public static ProgrammingBot getInstance() { - return instance; + @Override + public void onReady(@NotNull ReadyEvent readyEvent) { + logger.info("Bot is ready in '{}' server", readyEvent.getGuildTotalCount()); + + if (Database.isConnected()) + logger.info("Database is connected"); + else + logger.error("Database is not connected"); } - public ExecutorService getExecutor() { - return executor; + public void onDatabase() { + context = DSL.using(Database.getConnection(), SQLDialect.MARIADB); + + + if (Database.isConnected()) { + logger.info("Connected to database"); + } else { + logger.error("Failed to connect to database"); + } + + // closes the database when the bot is shut down + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + Database.disconnect(); + } catch (Exception e) { + logger.error("Failed to close database", e); + } + })); + } - @Override - public void onReady(@NotNull ReadyEvent readyEvent) { - logger.info("Bot is ready!"); + public static DSLContext getContext() { + return context; } } diff --git a/src/main/java/io/github/org/programming/bot/SlashCommandReg.java b/src/main/java/io/github/org/programming/bot/SlashCommandReg.java index b2724d1..4035e9d 100644 --- a/src/main/java/io/github/org/programming/bot/SlashCommandReg.java +++ b/src/main/java/io/github/org/programming/bot/SlashCommandReg.java @@ -1,17 +1,26 @@ package io.github.org.programming.bot; +import io.github.org.programming.backend.extension.SlashCommandExtender; import io.github.org.programming.backend.handler.SlashCommandHandler; -import io.github.org.programming.config.BotConfig; +import io.github.org.programming.bot.commands.ExampleCommand; +import io.github.org.programming.bot.config.BotConfig; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + public class SlashCommandReg extends SlashCommandHandler { protected SlashCommandReg(@NotNull JDA jda, @NotNull Guild guild) { super(jda, guild); + List extenders = new ArrayList<>(); + + extenders.add(new ExampleCommand()); + queueAndRegisterSlashCommands(extenders); } @Override diff --git a/src/main/java/io/github/org/programming/commands/ExampleCommand.java b/src/main/java/io/github/org/programming/bot/commands/ExampleCommand.java similarity index 77% rename from src/main/java/io/github/org/programming/commands/ExampleCommand.java rename to src/main/java/io/github/org/programming/bot/commands/ExampleCommand.java index 16f123c..8bfd259 100644 --- a/src/main/java/io/github/org/programming/commands/ExampleCommand.java +++ b/src/main/java/io/github/org/programming/bot/commands/ExampleCommand.java @@ -1,10 +1,9 @@ -package io.github.org.programming.commands; +package io.github.org.programming.bot.commands; import io.github.org.programming.backend.builder.slash.SlashCommand; import io.github.org.programming.backend.builder.slash.SlashCommandBuilder; import io.github.org.programming.backend.extension.SlashCommandExtender; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import org.jetbrains.annotations.NotNull; /** @@ -13,9 +12,6 @@ * This command basically replays with the gateway ping. */ public class ExampleCommand extends SlashCommandExtender { - protected ExampleCommand(SlashCommandData commandData) { - super(commandData); - } @Override public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) { @@ -23,7 +19,7 @@ public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent even } @Override - protected SlashCommand build() { + public SlashCommand build() { return new SlashCommandBuilder("ping", "Reply with the ping").build().setToGuildOnly(); } } diff --git a/src/main/java/io/github/org/programming/config/BotConfig.java b/src/main/java/io/github/org/programming/bot/config/BotConfig.java similarity index 91% rename from src/main/java/io/github/org/programming/config/BotConfig.java rename to src/main/java/io/github/org/programming/bot/config/BotConfig.java index 44ab9ee..89a6801 100644 --- a/src/main/java/io/github/org/programming/config/BotConfig.java +++ b/src/main/java/io/github/org/programming/bot/config/BotConfig.java @@ -1,4 +1,4 @@ -package io.github.org.programming.config; +package io.github.org.programming.bot.config; import io.github.yusufsdiscordbot.config.Config; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/io/github/org/programming/config/LoggerColorScheme.java b/src/main/java/io/github/org/programming/bot/config/LoggerColorScheme.java similarity index 96% rename from src/main/java/io/github/org/programming/config/LoggerColorScheme.java rename to src/main/java/io/github/org/programming/bot/config/LoggerColorScheme.java index 4d77867..18bd815 100644 --- a/src/main/java/io/github/org/programming/config/LoggerColorScheme.java +++ b/src/main/java/io/github/org/programming/bot/config/LoggerColorScheme.java @@ -1,4 +1,4 @@ -package io.github.org.programming.config; +package io.github.org.programming.bot.config; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; diff --git a/src/main/java/io/github/org/programming/database/Database.java b/src/main/java/io/github/org/programming/database/Database.java new file mode 100644 index 0000000..775cb29 --- /dev/null +++ b/src/main/java/io/github/org/programming/database/Database.java @@ -0,0 +1,82 @@ +package io.github.org.programming.database; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Objects; +import java.util.Properties; + +public class Database { + private static final Logger logger = LoggerFactory.getLogger(Database.class); + private static final HikariConfig config; + private static final HikariDataSource dataSource; + + static { + Properties props = new Properties(); + props.setProperty("dataSourceClassName", "org.postgresql.ds.PGSimpleDataSource"); + props.setProperty("dataSource.databaseName", DatabaseConfig.getDatabaseName()); + props.setProperty("dataSource.portNumber", String.valueOf(DatabaseConfig.getPortNumber())); + props.setProperty("dataSource.serverName", DatabaseConfig.getServerName()); + props.setProperty("dataSource.user", DatabaseConfig.getDatabaseUser()); + props.setProperty("dataSource.password", DatabaseConfig.getDatabasePassword()); + props.put("dataSource.logWriter", new PrintWriter(System.out)); + config = new HikariConfig(props); + dataSource = new HikariDataSource(config); + executeTableUpdate(); + } + + private static void executeTableUpdate() { + try (final Statement statement = + Objects.requireNonNull(getConnection(), "Connection is null").createStatement()) { + File folder = new File("src/main/resources/sql"); + File[] listOfFiles = folder.listFiles(); + + logger.info("Executing table updates..."); + + for (File file : listOfFiles) { + if (file.isFile()) { + logger.info("Executing update: {}", file.getName()); + String sql = new String(Files.readAllBytes(file.toPath())); + statement.execute(sql); + } + } + } catch (SQLException | IOException e) { + logger.error("Error executing table update", e); + } + } + + public static void disconnect() { + dataSource.close(); + } + + public static boolean isConnected() { + return (dataSource != null); + } + + + /** + * @return The connection to the database + */ + public static @Nullable Connection getConnection() { + try { + return dataSource.getConnection(); + } catch (SQLException e) { + logger.error("Error while getting connection", e); + return null; + } + } + + public static Logger getLogger() { + return logger; + } +} diff --git a/src/main/java/io/github/org/programming/database/DatabaseConfig.java b/src/main/java/io/github/org/programming/database/DatabaseConfig.java new file mode 100644 index 0000000..159e28a --- /dev/null +++ b/src/main/java/io/github/org/programming/database/DatabaseConfig.java @@ -0,0 +1,26 @@ +package io.github.org.programming.database; + +import io.github.yusufsdiscordbot.config.Config; + +public class DatabaseConfig { + + public static String getDatabaseUser() { + return Config.getString("DB_USER"); + } + + public static String getDatabasePassword() { + return Config.getString("DB_PASSWORD"); + } + + public static String getDatabaseName() { + return Config.getString("DATABASE_NAME"); + } + + public static int getPortNumber() { + return Config.getInt("PORT_NUMBER"); + } + + public static String getServerName() { + return Config.getString("SERVER_NAME"); + } +} diff --git a/src/main/java/io/github/org/programming/database/moderation/ModerationDatabase.java b/src/main/java/io/github/org/programming/database/moderation/ModerationDatabase.java new file mode 100644 index 0000000..d7c4275 --- /dev/null +++ b/src/main/java/io/github/org/programming/database/moderation/ModerationDatabase.java @@ -0,0 +1,21 @@ +package io.github.org.programming.database.moderation; + +import javax.annotation.Nullable; +import java.time.Instant; + +import static io.github.org.programming.bot.ProgrammingBot.getContext; +import static io.github.org.programming.jooq.tables.Moderation.MODERATION; + +public class ModerationDatabase { + + public static @Nullable Integer updateModerationDataBase(String guildId, String userId, + String moderationId, String reason) { + return getContext() + .insertInto(MODERATION, MODERATION.GUILD_ID, MODERATION.USER_ID, + MODERATION.MODERATOR_ID, MODERATION.TIME_STAMP, MODERATION.REASON) + .values(guildId, userId, moderationId, Instant.now(), reason) + .returning(MODERATION.ID) + .fetchOne(MODERATION.ID); + } + +} diff --git a/src/main/jooq/io/github/org/programming/jooq/DefaultCatalog.java b/src/main/jooq/io/github/org/programming/jooq/DefaultCatalog.java new file mode 100644 index 0000000..7d48fb2 --- /dev/null +++ b/src/main/jooq/io/github/org/programming/jooq/DefaultCatalog.java @@ -0,0 +1,45 @@ +/* + * This file is generated by jOOQ. + */ +package io.github.org.programming.jooq; + + +import java.util.Arrays; +import java.util.List; + +import org.jooq.Schema; +import org.jooq.impl.CatalogImpl; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class DefaultCatalog extends CatalogImpl { + + private static final long serialVersionUID = 1L; + + /** + * The reference instance of DEFAULT_CATALOG + */ + public static final DefaultCatalog DEFAULT_CATALOG = new DefaultCatalog(); + + /** + * The schema public. + */ + public final Public PUBLIC = Public.PUBLIC; + + /** + * No further instances allowed + */ + private DefaultCatalog() { + super(""); + } + + @Override + public final List getSchemas() { + return Arrays.asList( + Public.PUBLIC + ); + } +} diff --git a/src/main/jooq/io/github/org/programming/jooq/Keys.java b/src/main/jooq/io/github/org/programming/jooq/Keys.java new file mode 100644 index 0000000..5dea07e --- /dev/null +++ b/src/main/jooq/io/github/org/programming/jooq/Keys.java @@ -0,0 +1,28 @@ +/* + * This file is generated by jOOQ. + */ +package io.github.org.programming.jooq; + + +import io.github.org.programming.jooq.tables.Moderation; +import io.github.org.programming.jooq.tables.records.ModerationRecord; + +import org.jooq.TableField; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.Internal; + + +/** + * A class modelling foreign key relationships and constraints of tables in + * public. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Keys { + + // ------------------------------------------------------------------------- + // UNIQUE and PRIMARY KEY definitions + // ------------------------------------------------------------------------- + + public static final UniqueKey MODERATION_PKEY = Internal.createUniqueKey(Moderation.MODERATION, DSL.name("moderation_pkey"), new TableField[] { Moderation.MODERATION.ID }, true); +} diff --git a/src/main/jooq/io/github/org/programming/jooq/Public.java b/src/main/jooq/io/github/org/programming/jooq/Public.java new file mode 100644 index 0000000..f7fc2de --- /dev/null +++ b/src/main/jooq/io/github/org/programming/jooq/Public.java @@ -0,0 +1,54 @@ +/* + * This file is generated by jOOQ. + */ +package io.github.org.programming.jooq; + + +import io.github.org.programming.jooq.tables.Moderation; + +import java.util.Arrays; +import java.util.List; + +import org.jooq.Catalog; +import org.jooq.Table; +import org.jooq.impl.SchemaImpl; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Public extends SchemaImpl { + + private static final long serialVersionUID = 1L; + + /** + * The reference instance of public + */ + public static final Public PUBLIC = new Public(); + + /** + * The table public.moderation. + */ + public final Moderation MODERATION = Moderation.MODERATION; + + /** + * No further instances allowed + */ + private Public() { + super("public", null); + } + + + @Override + public Catalog getCatalog() { + return DefaultCatalog.DEFAULT_CATALOG; + } + + @Override + public final List> getTables() { + return Arrays.asList( + Moderation.MODERATION + ); + } +} diff --git a/src/main/jooq/io/github/org/programming/jooq/Tables.java b/src/main/jooq/io/github/org/programming/jooq/Tables.java new file mode 100644 index 0000000..673ff0a --- /dev/null +++ b/src/main/jooq/io/github/org/programming/jooq/Tables.java @@ -0,0 +1,20 @@ +/* + * This file is generated by jOOQ. + */ +package io.github.org.programming.jooq; + + +import io.github.org.programming.jooq.tables.Moderation; + + +/** + * Convenience access to all tables in public. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Tables { + + /** + * The table public.moderation. + */ + public static final Moderation MODERATION = Moderation.MODERATION; +} diff --git a/src/main/jooq/io/github/org/programming/jooq/tables/Moderation.java b/src/main/jooq/io/github/org/programming/jooq/tables/Moderation.java new file mode 100644 index 0000000..4cab1c1 --- /dev/null +++ b/src/main/jooq/io/github/org/programming/jooq/tables/Moderation.java @@ -0,0 +1,162 @@ +/* + * This file is generated by jOOQ. + */ +package io.github.org.programming.jooq.tables; + + +import io.github.org.programming.jooq.Keys; +import io.github.org.programming.jooq.Public; +import io.github.org.programming.jooq.tables.records.ModerationRecord; + +import java.time.Instant; + +import org.jooq.Field; +import org.jooq.ForeignKey; +import org.jooq.Identity; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.Row6; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.TableOptions; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; +import org.jooq.impl.TableImpl; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Moderation extends TableImpl { + + private static final long serialVersionUID = 1L; + + /** + * The reference instance of public.moderation + */ + public static final Moderation MODERATION = new Moderation(); + + /** + * The class holding records for this type + */ + @Override + public Class getRecordType() { + return ModerationRecord.class; + } + + /** + * The column public.moderation.id. + */ + public final TableField ID = createField(DSL.name("id"), SQLDataType.INTEGER.nullable(false).identity(true), this, ""); + + /** + * The column public.moderation.guild_id. + */ + public final TableField GUILD_ID = createField(DSL.name("guild_id"), SQLDataType.VARCHAR(255).nullable(false), this, ""); + + /** + * The column public.moderation.user_id. + */ + public final TableField USER_ID = createField(DSL.name("user_id"), SQLDataType.VARCHAR(255).nullable(false), this, ""); + + /** + * The column public.moderation.moderator_id. + */ + public final TableField MODERATOR_ID = createField(DSL.name("moderator_id"), SQLDataType.VARCHAR(255).nullable(false), this, ""); + + /** + * The column public.moderation.time_stamp. + */ + public final TableField TIME_STAMP = createField(DSL.name("time_stamp"), SQLDataType.INSTANT.nullable(false).defaultValue(DSL.field("CURRENT_TIMESTAMP", SQLDataType.INSTANT)), this, ""); + + /** + * The column public.moderation.reason. + */ + public final TableField REASON = createField(DSL.name("reason"), SQLDataType.VARCHAR(255).nullable(false), this, ""); + + private Moderation(Name alias, Table aliased) { + this(alias, aliased, null); + } + + private Moderation(Name alias, Table aliased, Field[] parameters) { + super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table()); + } + + /** + * Create an aliased public.moderation table reference + */ + public Moderation(String alias) { + this(DSL.name(alias), MODERATION); + } + + /** + * Create an aliased public.moderation table reference + */ + public Moderation(Name alias) { + this(alias, MODERATION); + } + + /** + * Create a public.moderation table reference + */ + public Moderation() { + this(DSL.name("moderation"), null); + } + + public Moderation(Table child, ForeignKey key) { + super(child, key, MODERATION); + } + + @Override + public Schema getSchema() { + return aliased() ? null : Public.PUBLIC; + } + + @Override + public Identity getIdentity() { + return (Identity) super.getIdentity(); + } + + @Override + public UniqueKey getPrimaryKey() { + return Keys.MODERATION_PKEY; + } + + @Override + public Moderation as(String alias) { + return new Moderation(DSL.name(alias), this); + } + + @Override + public Moderation as(Name alias) { + return new Moderation(alias, this); + } + + /** + * Rename this table + */ + @Override + public Moderation rename(String name) { + return new Moderation(DSL.name(name), null); + } + + /** + * Rename this table + */ + @Override + public Moderation rename(Name name) { + return new Moderation(name, null); + } + + // ------------------------------------------------------------------------- + // Row6 type methods + // ------------------------------------------------------------------------- + + @Override + public Row6 fieldsRow() { + return (Row6) super.fieldsRow(); + } +} diff --git a/src/main/jooq/io/github/org/programming/jooq/tables/pojos/Moderation.java b/src/main/jooq/io/github/org/programming/jooq/tables/pojos/Moderation.java new file mode 100644 index 0000000..4c573dc --- /dev/null +++ b/src/main/jooq/io/github/org/programming/jooq/tables/pojos/Moderation.java @@ -0,0 +1,107 @@ +/* + * This file is generated by jOOQ. + */ +package io.github.org.programming.jooq.tables.pojos; + + +import java.io.Serializable; +import java.time.Instant; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class Moderation implements Serializable { + + private static final long serialVersionUID = 1L; + + private final Integer id; + private final String guildId; + private final String userId; + private final String moderatorId; + private final Instant timeStamp; + private final String reason; + + public Moderation(Moderation value) { + this.id = value.id; + this.guildId = value.guildId; + this.userId = value.userId; + this.moderatorId = value.moderatorId; + this.timeStamp = value.timeStamp; + this.reason = value.reason; + } + + public Moderation( + Integer id, + String guildId, + String userId, + String moderatorId, + Instant timeStamp, + String reason + ) { + this.id = id; + this.guildId = guildId; + this.userId = userId; + this.moderatorId = moderatorId; + this.timeStamp = timeStamp; + this.reason = reason; + } + + /** + * Getter for public.moderation.id. + */ + public Integer getId() { + return this.id; + } + + /** + * Getter for public.moderation.guild_id. + */ + public String getGuildId() { + return this.guildId; + } + + /** + * Getter for public.moderation.user_id. + */ + public String getUserId() { + return this.userId; + } + + /** + * Getter for public.moderation.moderator_id. + */ + public String getModeratorId() { + return this.moderatorId; + } + + /** + * Getter for public.moderation.time_stamp. + */ + public Instant getTimeStamp() { + return this.timeStamp; + } + + /** + * Getter for public.moderation.reason. + */ + public String getReason() { + return this.reason; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Moderation ("); + + sb.append(id); + sb.append(", ").append(guildId); + sb.append(", ").append(userId); + sb.append(", ").append(moderatorId); + sb.append(", ").append(timeStamp); + sb.append(", ").append(reason); + + sb.append(")"); + return sb.toString(); + } +} diff --git a/src/main/jooq/io/github/org/programming/jooq/tables/records/ModerationRecord.java b/src/main/jooq/io/github/org/programming/jooq/tables/records/ModerationRecord.java new file mode 100644 index 0000000..1166f54 --- /dev/null +++ b/src/main/jooq/io/github/org/programming/jooq/tables/records/ModerationRecord.java @@ -0,0 +1,316 @@ +/* + * This file is generated by jOOQ. + */ +package io.github.org.programming.jooq.tables.records; + + +import io.github.org.programming.jooq.tables.Moderation; + +import java.time.Instant; + +import org.jooq.Field; +import org.jooq.Record1; +import org.jooq.Record6; +import org.jooq.Row6; +import org.jooq.impl.UpdatableRecordImpl; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class ModerationRecord extends UpdatableRecordImpl implements Record6 { + + private static final long serialVersionUID = 1L; + + /** + * Setter for public.moderation.id. + */ + public ModerationRecord setId(Integer value) { + set(0, value); + return this; + } + + /** + * Getter for public.moderation.id. + */ + public Integer getId() { + return (Integer) get(0); + } + + /** + * Setter for public.moderation.guild_id. + */ + public ModerationRecord setGuildId(String value) { + set(1, value); + return this; + } + + /** + * Getter for public.moderation.guild_id. + */ + public String getGuildId() { + return (String) get(1); + } + + /** + * Setter for public.moderation.user_id. + */ + public ModerationRecord setUserId(String value) { + set(2, value); + return this; + } + + /** + * Getter for public.moderation.user_id. + */ + public String getUserId() { + return (String) get(2); + } + + /** + * Setter for public.moderation.moderator_id. + */ + public ModerationRecord setModeratorId(String value) { + set(3, value); + return this; + } + + /** + * Getter for public.moderation.moderator_id. + */ + public String getModeratorId() { + return (String) get(3); + } + + /** + * Setter for public.moderation.time_stamp. + */ + public ModerationRecord setTimeStamp(Instant value) { + set(4, value); + return this; + } + + /** + * Getter for public.moderation.time_stamp. + */ + public Instant getTimeStamp() { + return (Instant) get(4); + } + + /** + * Setter for public.moderation.reason. + */ + public ModerationRecord setReason(String value) { + set(5, value); + return this; + } + + /** + * Getter for public.moderation.reason. + */ + public String getReason() { + return (String) get(5); + } + + // ------------------------------------------------------------------------- + // Primary key information + // ------------------------------------------------------------------------- + + @Override + public Record1 key() { + return (Record1) super.key(); + } + + // ------------------------------------------------------------------------- + // Record6 type implementation + // ------------------------------------------------------------------------- + + @Override + public Row6 fieldsRow() { + return (Row6) super.fieldsRow(); + } + + @Override + public Row6 valuesRow() { + return (Row6) super.valuesRow(); + } + + @Override + public Field field1() { + return Moderation.MODERATION.ID; + } + + @Override + public Field field2() { + return Moderation.MODERATION.GUILD_ID; + } + + @Override + public Field field3() { + return Moderation.MODERATION.USER_ID; + } + + @Override + public Field field4() { + return Moderation.MODERATION.MODERATOR_ID; + } + + @Override + public Field field5() { + return Moderation.MODERATION.TIME_STAMP; + } + + @Override + public Field field6() { + return Moderation.MODERATION.REASON; + } + + @Override + public Integer component1() { + return getId(); + } + + @Override + public String component2() { + return getGuildId(); + } + + @Override + public String component3() { + return getUserId(); + } + + @Override + public String component4() { + return getModeratorId(); + } + + @Override + public Instant component5() { + return getTimeStamp(); + } + + @Override + public String component6() { + return getReason(); + } + + @Override + public Integer value1() { + return getId(); + } + + @Override + public String value2() { + return getGuildId(); + } + + @Override + public String value3() { + return getUserId(); + } + + @Override + public String value4() { + return getModeratorId(); + } + + @Override + public Instant value5() { + return getTimeStamp(); + } + + @Override + public String value6() { + return getReason(); + } + + @Override + public ModerationRecord value1(Integer value) { + setId(value); + return this; + } + + @Override + public ModerationRecord value2(String value) { + setGuildId(value); + return this; + } + + @Override + public ModerationRecord value3(String value) { + setUserId(value); + return this; + } + + @Override + public ModerationRecord value4(String value) { + setModeratorId(value); + return this; + } + + @Override + public ModerationRecord value5(Instant value) { + setTimeStamp(value); + return this; + } + + @Override + public ModerationRecord value6(String value) { + setReason(value); + return this; + } + + @Override + public ModerationRecord values(Integer value1, String value2, String value3, String value4, Instant value5, String value6) { + value1(value1); + value2(value2); + value3(value3); + value4(value4); + value5(value5); + value6(value6); + return this; + } + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /** + * Create a detached ModerationRecord + */ + public ModerationRecord() { + super(Moderation.MODERATION); + } + + /** + * Create a detached, initialised ModerationRecord + */ + public ModerationRecord(Integer id, String guildId, String userId, String moderatorId, Instant timeStamp, String reason) { + super(Moderation.MODERATION); + + setId(id); + setGuildId(guildId); + setUserId(userId); + setModeratorId(moderatorId); + setTimeStamp(timeStamp); + setReason(reason); + } + + /** + * Create a detached, initialised ModerationRecord + */ + public ModerationRecord(io.github.org.programming.jooq.tables.pojos.Moderation value) { + super(Moderation.MODERATION); + + if (value != null) { + setId(value.getId()); + setGuildId(value.getGuildId()); + setUserId(value.getUserId()); + setModeratorId(value.getModeratorId()); + setTimeStamp(value.getTimeStamp()); + setReason(value.getReason()); + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index d0f5400..3f65cf3 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,6 +1,6 @@ - + diff --git a/src/main/resources/sql/moderation.sql b/src/main/resources/sql/moderation.sql new file mode 100644 index 0000000..02191e1 --- /dev/null +++ b/src/main/resources/sql/moderation.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS moderation +( + id serial not null primary key, + guild_id varchar(255) not null, + user_id varchar(255) not null, + moderator_id varchar(255) not null, + time_stamp timestamp not null default current_timestamp, + reason varchar(255) not null +) \ No newline at end of file