refactors and persistent server settings
This commit is contained in:
@@ -3,6 +3,7 @@ target/
|
|||||||
!**/src/main/**/target/
|
!**/src/main/**/target/
|
||||||
!**/src/test/**/target/
|
!**/src/test/**/target/
|
||||||
logs/
|
logs/
|
||||||
|
data/
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea/modules.xml
|
.idea/modules.xml
|
||||||
|
|||||||
Generated
+23
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="data" uuid="360fa9b4-f0d4-411d-8b92-fb946dc416e7">
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/data/data.db</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
<libraries>
|
||||||
|
<library>
|
||||||
|
<url>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</url>
|
||||||
|
</library>
|
||||||
|
<library>
|
||||||
|
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
|
||||||
|
</library>
|
||||||
|
</libraries>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package me.youhavetrouble.inviter;
|
package me.youhavetrouble.inviter;
|
||||||
|
|
||||||
import me.youhavetrouble.inviter.http.ApiServer;
|
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 me.youhavetrouble.inviter.storage.Storage;
|
||||||
import net.dv8tion.jda.api.JDA;
|
import net.dv8tion.jda.api.JDA;
|
||||||
import net.dv8tion.jda.api.JDABuilder;
|
import net.dv8tion.jda.api.JDABuilder;
|
||||||
@@ -18,8 +19,9 @@ public class Main {
|
|||||||
public static final Logger LOGGER = LoggerFactory.getLogger("Inviter");
|
public static final Logger LOGGER = LoggerFactory.getLogger("Inviter");
|
||||||
|
|
||||||
private static JDA jda;
|
private static JDA jda;
|
||||||
private static Storage storage;
|
private static DiscordInviteManager discordInviteManager;
|
||||||
private static ApiServer apiServer;
|
private static ApiServer apiServer;
|
||||||
|
private static Storage storage;
|
||||||
|
|
||||||
public static void main(String[] args) throws InterruptedException {
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
|
||||||
@@ -69,6 +71,8 @@ public class Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storage = new SqliteStorage();
|
||||||
|
|
||||||
jda = JDABuilder.create(
|
jda = JDABuilder.create(
|
||||||
token,
|
token,
|
||||||
Set.of(GatewayIntent.GUILD_INVITES)
|
Set.of(GatewayIntent.GUILD_INVITES)
|
||||||
@@ -86,7 +90,10 @@ public class Main {
|
|||||||
|
|
||||||
jda.awaitReady();
|
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 {
|
try {
|
||||||
apiServer = new ApiServer(hostname, port);
|
apiServer = new ApiServer(hostname, port);
|
||||||
@@ -98,8 +105,8 @@ public class Main {
|
|||||||
LOGGER.info("Welcome to the Inviter Application!");
|
LOGGER.info("Welcome to the Inviter Application!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Storage getStorage() {
|
public static DiscordInviteManager getDiscordInviteMenager() {
|
||||||
return storage;
|
return discordInviteManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package me.youhavetrouble.inviter;
|
package me.youhavetrouble.inviter.discord;
|
||||||
|
|
||||||
public record DiscordInvite(
|
public record DiscordInvite(
|
||||||
String code,
|
String code,
|
||||||
+3
-27
@@ -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.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import me.youhavetrouble.inviter.DiscordInvite;
|
|
||||||
import net.dv8tion.jda.api.JDA;
|
import net.dv8tion.jda.api.JDA;
|
||||||
import net.dv8tion.jda.api.entities.Guild;
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
import net.dv8tion.jda.api.entities.Invite;
|
import net.dv8tion.jda.api.entities.Invite;
|
||||||
import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion;
|
import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
public class MemoryStorage implements Storage {
|
public class DiscordInviteManager {
|
||||||
|
|
||||||
private final Cache<String, DiscordInvite> cache = Caffeine.newBuilder()
|
private final Cache<String, DiscordInvite> cache = Caffeine.newBuilder()
|
||||||
.expireAfterWrite(Duration.of(60, ChronoUnit.SECONDS))
|
.expireAfterWrite(Duration.of(60, ChronoUnit.SECONDS))
|
||||||
@@ -21,13 +19,11 @@ public class MemoryStorage implements Storage {
|
|||||||
|
|
||||||
private final JDA jda;
|
private final JDA jda;
|
||||||
|
|
||||||
public MemoryStorage(JDA jda) {
|
public DiscordInviteManager(JDA jda) {
|
||||||
this.jda = jda;
|
this.jda = jda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
|
||||||
public DiscordInvite getInvite(long guildId) {
|
public DiscordInvite getInvite(long guildId) {
|
||||||
DiscordInvite discordInvite = cache.getIfPresent(String.valueOf(guildId));
|
DiscordInvite discordInvite = cache.getIfPresent(String.valueOf(guildId));
|
||||||
if (discordInvite == null || discordInvite.isExpired()) {
|
if (discordInvite == null || discordInvite.isExpired()) {
|
||||||
@@ -52,24 +48,4 @@ public class MemoryStorage implements Storage {
|
|||||||
return discordInvite;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package me.youhavetrouble.inviter.discord;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public record GuildSettings(
|
||||||
|
boolean apiEnabled,
|
||||||
|
@Nullable String apiHostname
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
package me.youhavetrouble.inviter.http.endpoints;
|
package me.youhavetrouble.inviter.http.endpoints;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
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.Main;
|
||||||
import me.youhavetrouble.inviter.storage.Storage;
|
import me.youhavetrouble.inviter.discord.DiscordInviteManager;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -38,7 +38,7 @@ public class GetDiscordInviteByGuildId implements EndpointHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage storage = Main.getStorage();
|
DiscordInviteManager storage = Main.getDiscordInviteMenager();
|
||||||
DiscordInvite invite = storage.getInvite(guildIdLong);
|
DiscordInvite invite = storage.getInvite(guildIdLong);
|
||||||
|
|
||||||
if (invite == null) {
|
if (invite == null) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
package me.youhavetrouble.inviter.storage;
|
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.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public interface Storage {
|
public interface Storage {
|
||||||
|
|
||||||
@Nullable DiscordInvite getInvite(long guildId);
|
@NotNull GuildSettings getGuildSettings(long guildId);
|
||||||
|
|
||||||
/**
|
void saveDefaultGuildSettings(long guildId);
|
||||||
* Saves the invite to the storage and returns the saved invite.
|
|
||||||
* @param invite JDA invite object to save
|
void updateDiscordApiEnabled(long guildId, boolean enabled);
|
||||||
* @return the saved DiscordInvite object
|
|
||||||
*/
|
void updateDiscordApiHostname(long guildId, @Nullable String hostname);
|
||||||
@NotNull DiscordInvite saveInvite(Invite invite);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user