Skip to content

Commit

Permalink
Rewrite http module and start migrating to Exposed ORM (#28)
Browse files Browse the repository at this point in the history
* Add ktor

* Update java and add jetbrains exposed dependency

* Connect to exposed on startup

* Add mapped classes

* Add DiscordAPI.kt

* Add some routes

* Update java version in pull_request.yml workflow

* Fix compile error

* Fix serialization of APIError

* Finish guilds route

* Add more routes

* Rename .java to .kt

* Porting GuildData to Exposed DiscordGuild DAO

* Add GET and PATCH guild route, rewrote http error responses, also fixed logger

* Start WebhookAppender before db

* Check for nullability in PATCH /guilds/{id}

* Use GuildInfo for GET /guilds route

* respond with guild settings on PATCH /guilds/{id}

* add avatarUrl property to DiscordUser

* Add Leaderboard routes

* Update RemoveRankedMemberRoute.kt
  • Loading branch information
Xirado authored Apr 10, 2024
1 parent 0cf0412 commit fd7cbfd
Show file tree
Hide file tree
Showing 92 changed files with 2,162 additions and 2,737 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 16
java-version: 21
- uses: gradle/gradle-build-action@v2
with:
arguments: build
Expand Down
56 changes: 39 additions & 17 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import org.apache.tools.ant.filters.ReplaceTokens
import java.time.Instant

plugins {
id 'org.jetbrains.kotlin.jvm' version '1.6.20'
id 'org.jetbrains.kotlin.jvm' version '1.9.22'
id "org.jetbrains.kotlin.plugin.serialization" version "1.9.22"
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '7.0.0'
Expand All @@ -13,15 +14,15 @@ plugins {
mainClassName = 'at.xirado.bean.Bean'

group 'at.xirado'
version '5.0.0'
version '5.1.0'

shadowJar {
archiveFileName = "Bean.jar"
}

compileJava {
sourceCompatibility = JavaVersion.VERSION_16
targetCompatibility = JavaVersion.VERSION_16
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
options.encoding = 'UTF-8'
}

Expand All @@ -34,16 +35,16 @@ repositories {
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation 'net.dv8tion:JDA:5.0.0-beta.13'
implementation 'net.dv8tion:JDA:5.0.0-beta.21'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
implementation 'com.sparkjava:spark-core:2.9.3'
implementation 'org.codehaus.groovy:groovy-jsr223:3.0.8'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.mariadb.jdbc:mariadb-java-client:2.7.3'
implementation 'ch.qos.logback:logback-classic:1.3.0-alpha5'
implementation 'com.zaxxer:HikariCP:5.0.0'
implementation 'org.fusesource.jansi:jansi:2.4.0'

implementation 'org.slf4j:slf4j-api:2.0.12'
implementation 'ch.qos.logback:logback-classic:1.5.3'

implementation 'org.mariadb.jdbc:mariadb-java-client:3.3.3'
implementation 'com.zaxxer:HikariCP:5.1.0'

implementation 'com.google.guava:guava:31.0.1-jre'
implementation 'com.github.chew:jda-chewtils:5e1a9f93f9'
implementation 'org.scilab.forge:jlatexmath:1.0.7'
Expand All @@ -52,16 +53,38 @@ dependencies {
implementation 'com.github.minndevelopment:jda-ktx:17eb77a'
implementation 'net.jodah:expiringmap:0.5.10'
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
implementation 'org.jline:jline-style:3.21.0'

// Prometheus
implementation "io.prometheus:simpleclient:0.14.1"
implementation "io.prometheus:simpleclient_hotspot:0.14.1"
implementation "io.prometheus:simpleclient_httpserver:0.14.1"
implementation 'com.facebook:ktfmt:0.36'

runtimeOnly 'org.jetbrains.kotlin:kotlin-scripting-jsr223:1.6.21'
implementation 'dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm:2.3.8'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3'
implementation 'com.akuleshov7:ktoml-core:0.5.1'

def ktorVersion = "2.3.9"
def exposedVersion = "0.48.0"

implementation "io.ktor:ktor-server-core:${ktorVersion}"
implementation "io.ktor:ktor-server-netty:${ktorVersion}"
implementation "io.ktor:ktor-server-content-negotiation:${ktorVersion}"
implementation "io.ktor:ktor-serialization-kotlinx-json:${ktorVersion}"
implementation "io.ktor:ktor-server-auth:${ktorVersion}"
implementation "io.ktor:ktor-server-auth-jwt:${ktorVersion}"
implementation "io.ktor:ktor-server-cors:${ktorVersion}"
implementation "io.ktor:ktor-server-status-pages:${ktorVersion}"

implementation "org.jetbrains.exposed:exposed-core:${exposedVersion}"
implementation "org.jetbrains.exposed:exposed-crypt:${exposedVersion}"
implementation "org.jetbrains.exposed:exposed-dao:${exposedVersion}"
implementation "org.jetbrains.exposed:exposed-jdbc:${exposedVersion}"
implementation "org.jetbrains.exposed:exposed-json:${exposedVersion}"

implementation "com.sksamuel.aedile:aedile-core:1.3.1"

runtimeOnly 'org.jetbrains.kotlin:kotlin-scripting-jsr223:1.9.22'
}

/**
Expand All @@ -86,7 +109,6 @@ processResources {
}

compileKotlin {
kotlinOptions {
jvmTarget = "16"
}
kotlinOptions.jvmTarget = "21"
kotlinOptions.freeCompilerArgs = List.of("-Xcontext-receivers")
}
130 changes: 66 additions & 64 deletions src/main/java/at/xirado/bean/Bean.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
package at.xirado.bean;

import at.xirado.bean.backend.Authenticator;
import at.xirado.bean.backend.WebServer;
import at.xirado.bean.command.handler.CommandHandler;
import at.xirado.bean.command.handler.InteractionHandler;
import at.xirado.bean.data.OkHttpInterceptor;
import at.xirado.bean.data.content.DismissableContentManager;
import at.xirado.bean.data.database.Database;
import at.xirado.bean.data.repository.Repository;
import at.xirado.bean.event.*;
import at.xirado.bean.http.HttpServer;
import at.xirado.bean.http.oauth.DiscordAPI;
import at.xirado.bean.log.WebhookAppenderKt;
import at.xirado.bean.mee6.MEE6Queue;
import at.xirado.bean.misc.Util;
import at.xirado.bean.prometheus.MetricsJob;
import at.xirado.bean.prometheus.Prometheus;
import club.minnced.discord.webhook.WebhookClient;
import club.minnced.discord.webhook.WebhookClientBuilder;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import dev.reformator.stacktracedecoroutinator.runtime.DecoroutinatorRuntime;
import net.dv8tion.jda.api.GatewayEncoding;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Activity;
Expand All @@ -26,17 +27,12 @@
import net.dv8tion.jda.api.utils.ChunkingFilter;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.utils.IOUtil;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.auth.login.LoginException;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Properties;
import java.util.Set;
Expand All @@ -52,6 +48,7 @@ public class Bean {
public static final long START_TIME = System.currentTimeMillis() / 1000;

private static final Logger LOGGER = LoggerFactory.getLogger(Bean.class);
private static final String WEBHOOK_APPENDER_LAYOUT = "%boldGreen(%-15.-15logger{0}) %highlight(%-4level) %msg%n";
private static String VERSION;
private static long BUILD_TIME;
private static Bean instance;
Expand All @@ -74,41 +71,58 @@ public class Bean {
private final ExecutorService executor =
Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
.setNameFormat("Bean Executor Thread")
.setUncaughtExceptionHandler((t, e) -> LOGGER.error("An uncaught error occurred on the executor!", e))
.setUncaughtExceptionHandler((t, e) -> LOGGER.error("Uncaught exception in executor", e))
.build());

private final ExecutorService virtualThreadExecutor =
Executors.newThreadPerTaskExecutor(new ThreadFactoryBuilder()
.setThreadFactory(Thread.ofVirtual().factory())
.setUncaughtExceptionHandler((t, e) -> LOGGER.error("Uncaught exception in executor", e))
.build());

private final InteractionHandler interactionHandler;
private final CommandHandler commandHandler;
private final EventWaiter eventWaiter;
private final OkHttpClient okHttpClient;
private final WebServer webServer;
private final Authenticator authenticator;

private final HttpServer httpServer;

private final MEE6Queue mee6Queue;
private final DismissableContentManager dismissableContentManager;
private final Database database;
private final Repository repository;

private WebhookClient webhookClient = null;
private DataObject config = loadConfig();
private final Config config = ConfigKt.readConfig(true);

private final DiscordAPI discordApi;

public Bean() throws Exception {
instance = this;
Message.suppressContentIntentWarning();
Database.connect();
Database.awaitReady();
debug = !config.isNull("debug") && config.getBoolean("debug");

String webhookUrl = config.getWebhookUrl();

if (webhookUrl != null) {
WebhookAppenderKt.initWebhookLogger("info", webhookUrl, WEBHOOK_APPENDER_LAYOUT, 5000);
}

database = new Database(config.getDb());
repository = new Repository(database);

debug = config.getDebugMode();
interactionHandler = new InteractionHandler(this);
commandHandler = new CommandHandler();
eventWaiter = new EventWaiter();
Class.forName("at.xirado.bean.translation.LocaleLoader");

okHttpClient = new OkHttpClient.Builder()
.build();
if (!config.isNull("webhook_url"))
webhookClient = new WebhookClientBuilder(config.getString("webhook_url"))
.build();
if (config.isNull("token"))
throw new IllegalStateException("Can not start without a token!");
shardManager = DefaultShardManagerBuilder.create(config.getString("token"), getIntents())

setupShutdownHook();
shardManager = DefaultShardManagerBuilder.create(config.getDiscordToken(), getIntents())
.setShardsTotal(-1)
.setMemberCachePolicy(MemberCachePolicy.VOICE)
.setMemberCachePolicy(MemberCachePolicy.lru(500))
.setActivity(Activity.playing("bean.bz"))
.enableCache(CacheFlag.VOICE_STATE)
.setBulkDeleteSplittingEnabled(false)
Expand All @@ -122,11 +136,12 @@ public Bean() throws Exception {
eventWaiter, new GuildMemberJoinListener(), new DismissableContentButtonListener(), new GuildJoinListener(),
EvalListener.INSTANCE)
.build();
authenticator = new Authenticator();
String host = config.isNull("ip") ? "127.0.0.1" : config.getString("ip");
int port = config.isNull("port") ? 8887 : config.getInt("port");

webServer = new WebServer(host, port);
OAuthConfig oauthConfig = config.getOauth();

discordApi = new DiscordAPI(oauthConfig);
httpServer = new HttpServer(config);

dismissableContentManager = new DismissableContentManager();
new Prometheus();
new MetricsJob().start();
Expand All @@ -150,9 +165,9 @@ public static EnumSet<GatewayIntent> getIntents() {

public static void main(String[] args) {
Thread.currentThread().setName("Main");
DecoroutinatorRuntime.INSTANCE.load();
try {
loadPropertiesFile();
setupShutdownHook();
new Bean();
} catch (Exception e) {
if (e instanceof LoginException ex) {
Expand All @@ -169,7 +184,7 @@ private static void loadPropertiesFile() {
properties.load(Bean.class.getClassLoader().getResourceAsStream("app.properties"));
VERSION = properties.getProperty("app-version");
BUILD_TIME = Long.parseLong(properties.getProperty("build-time"));
} catch(Throwable e) {
} catch (Throwable e) {
LOGGER.error("Could not read app.properties file!");
VERSION = "0.0.0";
BUILD_TIME = 0L;
Expand Down Expand Up @@ -208,14 +223,10 @@ public static boolean isWhitelistedUser(long userId) {
return WHITELISTED_USERS.contains(userId);
}

public synchronized DataObject getConfig() {
public synchronized Config getConfig() {
return config;
}

public synchronized void updateConfig() {
this.config = loadConfig();
}

public ExecutorService getCommandExecutor() {
return commandExecutor;
}
Expand All @@ -228,6 +239,10 @@ public ExecutorService getExecutor() {
return executor;
}

public ExecutorService getVirtualThreadExecutor() {
return virtualThreadExecutor;
}

public boolean isDebug() {
return debug;
}
Expand All @@ -252,53 +267,40 @@ public OkHttpClient getOkHttpClient() {
return okHttpClient;
}

public Authenticator getAuthenticator() {
return authenticator;
}

public DismissableContentManager getDismissableContentManager() {
return dismissableContentManager;
}

private DataObject loadConfig() {
File configFile = new File("config.json");
if (!configFile.exists()) {
InputStream inputStream = Bean.class.getResourceAsStream("/config.json");
if (inputStream == null) {
LOGGER.error("Could not copy config from resources folder!");
return DataObject.empty();
}
Path path = Paths.get(Util.getJarPath() + "/config.json");
try {
Files.copy(inputStream, path);
} catch (IOException e) {
LOGGER.error("Could not copy config file!", e);
}
}
try {
return DataObject.fromJson(new FileInputStream(configFile));
} catch (FileNotFoundException ignored) {
}
return DataObject.empty();
public DiscordAPI getDiscordApi() {
return discordApi;
}

public WebServer getWebServer() {
return webServer;
public HttpServer getHttpServer() {
return httpServer;
}

public MEE6Queue getMEE6Queue() {
return mee6Queue;
}

private static void setupShutdownHook() {
public Database getDatabase() {
return database;
}

public Repository getRepository() {
return repository;
}

private void setupShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
LOGGER.info("Awaiting JDA ShardManager shutdown...");
getInstance().getShardManager().shutdown();
for (JDA jda : getInstance().getShardManager().getShards()) {
getShardManager().shutdown();
for (JDA jda : getShardManager().getShards()) {
while (jda.getStatus() != JDA.Status.SHUTDOWN) {
try {
Thread.sleep(20);
} catch (InterruptedException ignored) {}
} catch (InterruptedException ignored) {
}
}
}
LOGGER.info("Stopped all shards");
Expand Down
Loading

0 comments on commit fd7cbfd

Please sign in to comment.