diff --git a/.gitignore b/.gitignore
index 0f3cac9..4fb2032 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target/
!**/src/main/**/target/
!**/src/test/**/target/
logs/
+data/
### IntelliJ IDEA ###
.idea/modules.xml
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..61012ce
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/data/data.db
+
+
+
+ $ProjectFileDir$
+
+
+ file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar
+
+
+ file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/me/youhavetrouble/inviter/Main.java b/src/main/java/me/youhavetrouble/inviter/Main.java
index 4f5597a..d00064e 100644
--- a/src/main/java/me/youhavetrouble/inviter/Main.java
+++ b/src/main/java/me/youhavetrouble/inviter/Main.java
@@ -1,7 +1,8 @@
package me.youhavetrouble.inviter;
import me.youhavetrouble.inviter.http.ApiServer;
-import me.youhavetrouble.inviter.storage.MemoryStorage;
+import me.youhavetrouble.inviter.discord.DiscordInviteManager;
+import me.youhavetrouble.inviter.storage.SqliteStorage;
import me.youhavetrouble.inviter.storage.Storage;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
@@ -18,8 +19,9 @@ public class Main {
public static final Logger LOGGER = LoggerFactory.getLogger("Inviter");
private static JDA jda;
- private static Storage storage;
+ private static DiscordInviteManager discordInviteManager;
private static ApiServer apiServer;
+ private static Storage storage;
public static void main(String[] args) throws InterruptedException {
@@ -69,6 +71,8 @@ public class Main {
}
}
+ storage = new SqliteStorage();
+
jda = JDABuilder.create(
token,
Set.of(GatewayIntent.GUILD_INVITES)
@@ -86,7 +90,10 @@ public class Main {
jda.awaitReady();
- storage = new MemoryStorage(jda);
+ jda.getGuilds().parallelStream().forEach(guild -> storage.saveDefaultGuildSettings(guild.getIdLong()));
+ // TODO make sure to save default settings for guilds bot joins on runtime
+
+ discordInviteManager = new DiscordInviteManager(jda);
try {
apiServer = new ApiServer(hostname, port);
@@ -98,8 +105,8 @@ public class Main {
LOGGER.info("Welcome to the Inviter Application!");
}
- public static Storage getStorage() {
- return storage;
+ public static DiscordInviteManager getDiscordInviteMenager() {
+ return discordInviteManager;
}
}
diff --git a/src/main/java/me/youhavetrouble/inviter/DiscordInvite.java b/src/main/java/me/youhavetrouble/inviter/discord/DiscordInvite.java
similarity index 94%
rename from src/main/java/me/youhavetrouble/inviter/DiscordInvite.java
rename to src/main/java/me/youhavetrouble/inviter/discord/DiscordInvite.java
index 2c2e746..b193387 100644
--- a/src/main/java/me/youhavetrouble/inviter/DiscordInvite.java
+++ b/src/main/java/me/youhavetrouble/inviter/discord/DiscordInvite.java
@@ -1,4 +1,4 @@
-package me.youhavetrouble.inviter;
+package me.youhavetrouble.inviter.discord;
public record DiscordInvite(
String code,
diff --git a/src/main/java/me/youhavetrouble/inviter/storage/MemoryStorage.java b/src/main/java/me/youhavetrouble/inviter/discord/DiscordInviteManager.java
similarity index 65%
rename from src/main/java/me/youhavetrouble/inviter/storage/MemoryStorage.java
rename to src/main/java/me/youhavetrouble/inviter/discord/DiscordInviteManager.java
index 8557ff5..7ff76f3 100644
--- a/src/main/java/me/youhavetrouble/inviter/storage/MemoryStorage.java
+++ b/src/main/java/me/youhavetrouble/inviter/discord/DiscordInviteManager.java
@@ -1,19 +1,17 @@
-package me.youhavetrouble.inviter.storage;
+package me.youhavetrouble.inviter.discord;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
-import me.youhavetrouble.inviter.DiscordInvite;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Invite;
import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
-public class MemoryStorage implements Storage {
+public class DiscordInviteManager {
private final Cache cache = Caffeine.newBuilder()
.expireAfterWrite(Duration.of(60, ChronoUnit.SECONDS))
@@ -21,13 +19,11 @@ public class MemoryStorage implements Storage {
private final JDA jda;
- public MemoryStorage(JDA jda) {
+ public DiscordInviteManager(JDA jda) {
this.jda = jda;
}
-
@Nullable
- @Override
public DiscordInvite getInvite(long guildId) {
DiscordInvite discordInvite = cache.getIfPresent(String.valueOf(guildId));
if (discordInvite == null || discordInvite.isExpired()) {
@@ -52,24 +48,4 @@ public class MemoryStorage implements Storage {
return discordInvite;
}
- @NotNull
- @Override
- public DiscordInvite saveInvite(Invite invite) {
-
- if (invite == null) {
- throw new IllegalArgumentException("Invite cannot be null");
- }
- if (invite.getGuild() == null) {
- throw new IllegalArgumentException("Invite must be associated with a guild");
- }
-
- DiscordInvite discordInvite = new DiscordInvite(
- invite.getCode(),
- invite.getGuild().getIdLong(),
- invite.getTimeCreated().toEpochSecond()
- );
-
- cache.put(invite.getGuild().getId(), discordInvite);
- return discordInvite;
- }
}
diff --git a/src/main/java/me/youhavetrouble/inviter/discord/GuildSettings.java b/src/main/java/me/youhavetrouble/inviter/discord/GuildSettings.java
new file mode 100644
index 0000000..eb10d21
--- /dev/null
+++ b/src/main/java/me/youhavetrouble/inviter/discord/GuildSettings.java
@@ -0,0 +1,10 @@
+package me.youhavetrouble.inviter.discord;
+
+import org.jetbrains.annotations.Nullable;
+
+public record GuildSettings(
+ boolean apiEnabled,
+ @Nullable String apiHostname
+) {
+
+}
diff --git a/src/main/java/me/youhavetrouble/inviter/http/endpoints/GetDiscordInviteByGuildId.java b/src/main/java/me/youhavetrouble/inviter/http/endpoints/GetDiscordInviteByGuildId.java
index e01d67f..247cbdf 100644
--- a/src/main/java/me/youhavetrouble/inviter/http/endpoints/GetDiscordInviteByGuildId.java
+++ b/src/main/java/me/youhavetrouble/inviter/http/endpoints/GetDiscordInviteByGuildId.java
@@ -1,9 +1,9 @@
package me.youhavetrouble.inviter.http.endpoints;
import com.sun.net.httpserver.HttpExchange;
-import me.youhavetrouble.inviter.DiscordInvite;
+import me.youhavetrouble.inviter.discord.DiscordInvite;
import me.youhavetrouble.inviter.Main;
-import me.youhavetrouble.inviter.storage.Storage;
+import me.youhavetrouble.inviter.discord.DiscordInviteManager;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@@ -38,7 +38,7 @@ public class GetDiscordInviteByGuildId implements EndpointHandler {
return;
}
- Storage storage = Main.getStorage();
+ DiscordInviteManager storage = Main.getDiscordInviteMenager();
DiscordInvite invite = storage.getInvite(guildIdLong);
if (invite == null) {
diff --git a/src/main/java/me/youhavetrouble/inviter/storage/SqliteStorage.java b/src/main/java/me/youhavetrouble/inviter/storage/SqliteStorage.java
new file mode 100644
index 0000000..a273ebb
--- /dev/null
+++ b/src/main/java/me/youhavetrouble/inviter/storage/SqliteStorage.java
@@ -0,0 +1,121 @@
+package me.youhavetrouble.inviter.storage;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import me.youhavetrouble.inviter.discord.GuildSettings;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.sql.DataSource;
+import java.io.File;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SqliteStorage implements Storage {
+
+ private final DataSource dataSource;
+
+ public SqliteStorage() {
+ File dataFolder = new File("data");
+ if (!dataFolder.exists()) {
+ if (!dataFolder.mkdirs()) {
+ throw new RuntimeException("Failed to create data folder");
+ }
+ }
+
+ HikariConfig config = new HikariConfig();
+ config.setPoolName("DataSQLitePool");
+ config.setDriverClassName("org.sqlite.JDBC");
+ config.setJdbcUrl("jdbc:sqlite:data/data.db");
+ config.setConnectionTestQuery("PRAGMA journal_mode=WAL;");
+ config.setMaxLifetime(60000); // 60 Sec
+ config.setMaximumPoolSize(Math.min(4, Runtime.getRuntime().availableProcessors() / 4));
+ dataSource = new HikariDataSource(config);
+
+ try (Connection connection = dataSource.getConnection()) {
+ // Initialize the database schema if necessary
+ // For example, you might want to create a table for guild settings
+ connection.createStatement().execute("""
+ CREATE TABLE IF NOT EXISTS guild_settings (
+ guild_id LONG PRIMARY KEY,
+ api_enabled BOOLEAN NOT NULL DEFAULT FALSE,
+ api_hostname VARCHAR(256) DEFAULT NULL
+ );
+ """
+ );
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to initialize database", e);
+ }
+
+ }
+
+
+ @NotNull
+ @Override
+ public GuildSettings getGuildSettings(long guildId) {
+ try (Connection connection = dataSource.getConnection()) {
+ var statement = connection.prepareStatement(
+ "SELECT * FROM guild_settings WHERE guild_id = ?"
+ );
+ statement.setLong(1, guildId);
+ ResultSet resultSet = statement.executeQuery();
+
+ if (resultSet.next()) {
+ boolean apiEnabled = resultSet.getBoolean("api_enabled");
+ String apiHostname = resultSet.getString("api_hostname");
+ return new GuildSettings(apiEnabled, apiHostname);
+ }
+ return new GuildSettings(false, null);
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to retrieve guild settings", e);
+
+ }
+ }
+
+ @Override
+ public void saveDefaultGuildSettings(long guildId) {
+ try (Connection connection = dataSource.getConnection()) {
+ var statement = connection.prepareStatement(
+ "INSERT OR IGNORE INTO guild_settings (guild_id) VALUES (?)"
+ );
+ statement.setLong(1, guildId);
+ statement.executeUpdate();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to save default guild settings", e);
+ }
+ }
+
+ @Override
+ public void updateDiscordApiEnabled(long guildId, boolean enabled) {
+ try (Connection connection = dataSource.getConnection()) {
+ var statement = connection.prepareStatement(
+ "UPDATE guild_settings SET api_enabled = ? WHERE guild_id = ?"
+ );
+ statement.setBoolean(1, enabled);
+ statement.setLong(2, guildId);
+ statement.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to update Discord API enabled status", e);
+ }
+
+ }
+
+ @Override
+ public void updateDiscordApiHostname(long guildId, @Nullable String hostname) {
+ try (Connection connection = dataSource.getConnection()) {
+ var statement = connection.prepareStatement(
+ "UPDATE guild_settings SET api_hostname = ? WHERE guild_id = ?"
+ );
+ if (hostname == null || hostname.isEmpty()) {
+ statement.setNull(1, java.sql.Types.VARCHAR);
+ } else {
+ statement.setString(1, hostname);
+ }
+ statement.setLong(2, guildId);
+ statement.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to update Discord API hostname", e);
+ }
+ }
+}
diff --git a/src/main/java/me/youhavetrouble/inviter/storage/Storage.java b/src/main/java/me/youhavetrouble/inviter/storage/Storage.java
index e6f74b9..72e8322 100644
--- a/src/main/java/me/youhavetrouble/inviter/storage/Storage.java
+++ b/src/main/java/me/youhavetrouble/inviter/storage/Storage.java
@@ -1,19 +1,18 @@
package me.youhavetrouble.inviter.storage;
-import me.youhavetrouble.inviter.DiscordInvite;
-import net.dv8tion.jda.api.entities.Invite;
+
+import me.youhavetrouble.inviter.discord.GuildSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface Storage {
- @Nullable DiscordInvite getInvite(long guildId);
+ @NotNull GuildSettings getGuildSettings(long guildId);
- /**
- * Saves the invite to the storage and returns the saved invite.
- * @param invite JDA invite object to save
- * @return the saved DiscordInvite object
- */
- @NotNull DiscordInvite saveInvite(Invite invite);
+ void saveDefaultGuildSettings(long guildId);
+
+ void updateDiscordApiEnabled(long guildId, boolean enabled);
+
+ void updateDiscordApiHostname(long guildId, @Nullable String hostname);
}