Rewrite the database system to have a proper ORM and migrations

This commit is contained in:
2026-05-19 02:02:55 -04:00
parent a508aa5aae
commit a980e26cc7
33 changed files with 1154 additions and 941 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ plugins {
}
group = "dev.plex"
version = "1.7-SNAPSHOT"
version = "2.0-SNAPSHOT"
description = "Plex"
subprojects {
+3
View File
@@ -23,7 +23,10 @@ dependencies {
library("commons-io:commons-io:2.22.0")
library("redis.clients:jedis:7.5.0")
library("org.mariadb.jdbc:mariadb-java-client:3.5.8")
library("org.postgresql:postgresql:42.7.11")
library("org.xerial:sqlite-jdbc:3.53.1.0")
library("com.zaxxer:HikariCP:7.0.2")
library("com.j256.ormlite:ormlite-jdbc:6.1")
library("org.jetbrains:annotations:26.1.0")
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+")
compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") {
+6 -1
View File
@@ -112,7 +112,7 @@ public class Plex extends JavaPlugin
playerCache = new PlayerCache();
PlexLog.log("Attempting to connect to DB: {0}", plugin.config.getString("data.central.db"));
PlexLog.log("Attempting to connect to DB: {0}", plugin.config.getString("data.db.name"));
try
{
PlexUtils.testConnections();
@@ -226,6 +226,11 @@ public class Plex extends JavaPlugin
this.getServer().getMessenger().unregisterOutgoingPluginChannel(this);
moduleManager.disableModules();
if (sqlConnection != null)
{
sqlConnection.close();
}
}
private void generateWorlds()
@@ -4,8 +4,6 @@ import dev.plex.Plex;
import dev.plex.util.PlexLog;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -17,22 +15,17 @@ public class Config extends YamlConfiguration
/**
* The plugin instance
*/
private Plex plugin;
private final Plex plugin;
/**
* The File instance
*/
private File file;
private final File file;
/**
* The file name
*/
private String name;
/**
* Whether new entries were added to the file automatically
*/
private boolean added = false;
private final String name;
/**
* Creates a config object
@@ -60,37 +53,20 @@ public class Config extends YamlConfiguration
/**
* Loads the configuration file
*/
public void load(boolean loadFromFile)
public void load(boolean reconcileWithDefaults)
{
try
{
if (loadFromFile)
if (reconcileWithDefaults)
{
YamlConfiguration externalYamlConfig = YamlConfiguration.loadConfiguration(file);
InputStreamReader internalConfigFileStream = new InputStreamReader(plugin.getResource(name), StandardCharsets.UTF_8);
YamlConfiguration internalYamlConfig = YamlConfiguration.loadConfiguration(internalConfigFileStream);
// Gets all the keys inside the internal file and iterates through all of it's key pairs
for (String string : internalYamlConfig.getKeys(true))
ConfigDefaultsMerger.Result result = ConfigDefaultsMerger.merge(file, plugin.getResource(name), name);
if (!result.addedKeys().isEmpty())
{
// Checks if the external file contains the key already.
if (!externalYamlConfig.contains(string))
{
// If it doesn't contain the key, we set the key based off what was found inside the plugin jar
externalYamlConfig.setComments(string, internalYamlConfig.getComments(string));
externalYamlConfig.setInlineComments(string, internalYamlConfig.getInlineComments(string));
externalYamlConfig.set(string, internalYamlConfig.get(string));
PlexLog.log("Setting key: " + string + " in " + this.name + " to the default value(s) since it does not exist!");
added = true;
}
}
if (added)
{
externalYamlConfig.save(file);
PlexLog.log("Saving new file...");
added = false;
PlexLog.log("Merged default key(s) into " + name + ": " + String.join(", ", result.addedKeys()));
}
}
this.options().parseComments(true);
super.load(file);
}
catch (Exception ex)
@@ -0,0 +1,220 @@
package dev.plex.config;
import dev.plex.util.PlexLog;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final class ConfigDefaultsMerger
{
private static final Pattern KEY = Pattern.compile("^(\\s*)([A-Za-z0-9_-]+):(?:\\s+.*)?$");
private ConfigDefaultsMerger()
{
}
static Result merge(File file, InputStream defaultsStream, String displayName) throws IOException, InvalidConfigurationException
{
if (defaultsStream == null)
{
PlexLog.warn("Unable to merge defaults into " + displayName + " because no default resource exists.");
return new Result(load(file), List.of(), false);
}
String defaultsText = new String(defaultsStream.readAllBytes(), StandardCharsets.UTF_8);
List<String> defaultLines = splitLines(defaultsText);
List<String> currentLines = splitLines(Files.readString(file.toPath(), StandardCharsets.UTF_8));
Map<String, Entry> defaults = parse(defaultLines);
Map<String, Entry> current = parse(currentLines);
List<Insertion> insertions = new ArrayList<>();
Set<String> coveredByMissingParent = new HashSet<>();
int order = 0;
for (Entry entry : defaults.values())
{
if (current.containsKey(entry.path) || isCovered(entry.path, coveredByMissingParent))
{
order++;
continue;
}
String parent = parent(entry.path);
if (parent != null && !current.containsKey(parent))
{
order++;
continue;
}
insertions.add(new Insertion(findInsertionIndex(entry, defaults, current, currentLines.size()), order, entry.path, defaultLines.subList(entry.start, entry.end)));
coveredByMissingParent.add(entry.path);
order++;
}
if (insertions.isEmpty())
{
return new Result(load(file), List.of(), false);
}
applyInsertions(currentLines, insertions);
Files.writeString(file.toPath(), String.join(System.lineSeparator(), currentLines) + System.lineSeparator(), StandardCharsets.UTF_8);
return new Result(load(file), insertions.stream().map(Insertion::path).toList(), true);
}
private static int findInsertionIndex(Entry missing, Map<String, Entry> defaults, Map<String, Entry> current, int fallback)
{
String parent = parent(missing.path);
for (Entry candidate : defaults.values())
{
if (candidate.start <= missing.start || !sameParent(missing.path, candidate.path))
{
continue;
}
Entry existingCandidate = current.get(candidate.path);
if (existingCandidate != null)
{
return existingCandidate.start;
}
}
if (parent != null && current.containsKey(parent))
{
return current.get(parent).end;
}
return fallback;
}
private static void applyInsertions(List<String> currentLines, List<Insertion> insertions)
{
insertions.sort(Comparator.comparingInt(Insertion::index).reversed().thenComparing(Comparator.comparingInt(Insertion::order).reversed()));
for (Insertion insertion : insertions)
{
List<String> block = new ArrayList<>(insertion.lines);
if (insertion.index > 0 && !currentLines.get(insertion.index - 1).isBlank() && !block.get(0).isBlank())
{
block.add(0, "");
}
if (insertion.index < currentLines.size() && !currentLines.get(insertion.index).isBlank() && !block.get(block.size() - 1).isBlank())
{
block.add("");
}
currentLines.addAll(insertion.index, block);
}
}
private static Map<String, Entry> parse(List<String> lines)
{
Map<String, Entry> entries = new LinkedHashMap<>();
List<StackEntry> stack = new ArrayList<>();
for (int i = 0; i < lines.size(); i++)
{
Matcher matcher = KEY.matcher(lines.get(i));
if (!matcher.matches() || lines.get(i).trim().startsWith("#"))
{
continue;
}
int indent = matcher.group(1).length();
String key = matcher.group(2);
while (!stack.isEmpty() && stack.get(stack.size() - 1).indent >= indent)
{
stack.remove(stack.size() - 1);
}
String path = stack.isEmpty() ? key : stack.get(stack.size() - 1).path + "." + key;
entries.put(path, new Entry(path, blockStart(lines, i), blockEnd(lines, i, indent), indent));
stack.add(new StackEntry(path, indent));
}
return entries;
}
private static int blockStart(List<String> lines, int keyLine)
{
int start = keyLine;
while (start > 0)
{
String previous = lines.get(start - 1).trim();
if (!previous.isBlank() && !previous.startsWith("#"))
{
break;
}
start--;
}
return start;
}
private static int blockEnd(List<String> lines, int keyLine, int indent)
{
for (int i = keyLine + 1; i < lines.size(); i++)
{
Matcher matcher = KEY.matcher(lines.get(i));
if (matcher.matches() && matcher.group(1).length() <= indent)
{
return blockStart(lines, i);
}
}
return lines.size();
}
private static boolean sameParent(String first, String second)
{
String firstParent = parent(first);
String secondParent = parent(second);
return firstParent == null ? secondParent == null : firstParent.equals(secondParent);
}
private static boolean isCovered(String path, Set<String> covered)
{
return covered.stream().anyMatch(parent -> path.startsWith(parent + "."));
}
private static String parent(String path)
{
int index = path.lastIndexOf('.');
return index == -1 ? null : path.substring(0, index);
}
private static List<String> splitLines(String text)
{
return new ArrayList<>(text.lines().toList());
}
static YamlConfiguration load(File file) throws IOException, InvalidConfigurationException
{
YamlConfiguration config = new YamlConfiguration();
config.options().parseComments(true);
config.load(file);
return config;
}
private record StackEntry(String path, int indent)
{
}
private record Entry(String path, int start, int end, int indent)
{
}
private record Insertion(int index, int order, String path, List<String> lines)
{
}
record Result(YamlConfiguration config, List<String> addedKeys, boolean changed)
{
}
}
@@ -1,9 +1,11 @@
package dev.plex.config;
import dev.plex.module.PlexModule;
import dev.plex.util.PlexLog;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import org.bukkit.configuration.InvalidConfigurationException;
@@ -17,22 +19,22 @@ public class ModuleConfig extends YamlConfiguration
/**
* The plugin instance
*/
private PlexModule module;
private final PlexModule module;
/**
* The File instance
*/
private File file;
private final File file;
/**
* Where the file is in the module JAR
*/
private String from;
private final String from;
/**
* Where it should be copied to in the module folder
*/
private String to;
private final String to;
/**
* Creates a config object
@@ -57,6 +59,13 @@ public class ModuleConfig extends YamlConfiguration
{
try
{
ConfigDefaultsMerger.Result result = ConfigDefaultsMerger.merge(file, module.getClass().getResourceAsStream("/" + from), to);
if (!result.addedKeys().isEmpty())
{
PlexLog.log("Merged default key(s) into " + to + ": " + String.join(", ", result.addedKeys()));
}
this.options().parseComments(true);
super.load(file);
}
catch (IOException | InvalidConfigurationException ex)
@@ -87,7 +96,20 @@ public class ModuleConfig extends YamlConfiguration
{
try
{
Files.copy(module.getClass().getResourceAsStream("/" + from), this.file.toPath());
File parent = file.getParentFile();
if (parent != null)
{
parent.mkdirs();
}
try (InputStream stream = module.getClass().getResourceAsStream("/" + from))
{
if (stream == null)
{
PlexLog.warn("Unable to save default module config " + to + ": missing resource " + from);
return;
}
Files.copy(stream, this.file.toPath());
}
}
catch (IOException e)
{
@@ -28,7 +28,7 @@ public class PunishedPlayerMenu extends PageableMenu<Punishment>
@Override
protected ItemStack toItem(Punishment object)
{
return new ItemBuilder(Material.PAPER).displayName("<!italic><red>" + object.getType().name()).lore("<!italic><red>By: <gold>" + (object.getPunisher() == null ? "CONSOLE" : Plex.get().getSqlPlayerData().getNameByUUID(object.getPunisher())), "<!italic><red>Expire(d/s): <gold>" + TimeUtils.useTimezone(object.getEndDate()), "<!italic><red>Reason: <gold>" + object.getReason()).build();
return new ItemBuilder(Material.PAPER).displayName("<!italic><red>" + object.getType().name()).lore("<!italic><red>By: <gold>" + (object.getPunisher() == null ? "CONSOLE" : Plex.get().getSqlPlayerData().getNameByUUID(object.getPunisher())), "<!italic><red>Issued: <gold>" + TimeUtils.useTimezone(object.getIssueDate()), "<!italic><red>Expire(d/s): <gold>" + TimeUtils.useTimezone(object.getEndDate()), "<!italic><red>Reason: <gold>" + object.getReason()).build();
}
@Override
@@ -6,10 +6,6 @@ import dev.plex.Plex;
import dev.plex.punishment.Punishment;
import dev.plex.punishment.PunishmentType;
import dev.plex.punishment.extra.Note;
import dev.plex.storage.annotation.MapObjectList;
import dev.plex.storage.annotation.PrimaryKey;
import dev.plex.storage.annotation.SQLTable;
import dev.plex.storage.annotation.VarcharLimit;
import dev.plex.util.adapter.ZonedDateTimeAdapter;
import java.time.ZonedDateTime;
@@ -26,15 +22,12 @@ import org.jetbrains.annotations.NotNull;
@Getter
@Setter
@SQLTable("players")
public class PlexPlayer
{
@Setter(AccessLevel.NONE)
@PrimaryKey
@NotNull
private UUID uuid;
@VarcharLimit(16)
@NotNull
private String name;
@@ -54,10 +47,8 @@ public class PlexPlayer
private List<String> ips = Lists.newArrayList();
@MapObjectList
private List<Punishment> punishments = Lists.newArrayList();
@MapObjectList
private List<Note> notes = Lists.newArrayList();
public PlexPlayer()
@@ -3,11 +3,11 @@ package dev.plex.punishment;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import dev.plex.Plex;
import dev.plex.storage.annotation.SQLTable;
import dev.plex.util.PlexUtils;
import dev.plex.util.TimeUtils;
import dev.plex.util.adapter.ZonedDateTimeAdapter;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.UUID;
@@ -18,7 +18,6 @@ import org.jetbrains.annotations.NotNull;
@Getter
@Setter
@SQLTable("punishments")
public class Punishment
{
private static final Gson gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()).create();
@@ -36,12 +35,14 @@ public class Punishment
private String reason;
private boolean customTime;
private boolean active; // Field is only for bans
private ZonedDateTime issueDate;
private ZonedDateTime endDate;
public Punishment(UUID punished, UUID punisher)
{
this.punished = punished;
this.punisher = punisher;
this.issueDate = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
}
public static Component generateBanMessage(Punishment punishment)
@@ -1,8 +1,6 @@
package dev.plex.punishment.extra;
import com.google.gson.GsonBuilder;
import dev.plex.storage.annotation.NoLimit;
import dev.plex.storage.annotation.SQLTable;
import dev.plex.util.adapter.ZonedDateTimeAdapter;
import java.time.ZonedDateTime;
@@ -11,12 +9,10 @@ import java.util.UUID;
import lombok.Data;
@Data
@SQLTable("notes")
public class Note
{
private final UUID uuid;
@NoLimit
private final String note;
private final UUID writtenBy;
private final ZonedDateTime timestamp;
@@ -14,11 +14,11 @@ public class RedisConnection implements PlexBase
{
try
{
jedis = new Jedis(plugin.config.getString("data.side.hostname"),
plugin.config.getInt("data.side.port"));
if (plugin.config.getBoolean("data.side.auth"))
jedis = new Jedis(plugin.config.getString("data.redis.hostname"),
plugin.config.getInt("data.redis.port"));
if (plugin.config.getBoolean("data.redis.auth"))
{
jedis.auth(plugin.config.getString("data.side.password"));
jedis.auth(plugin.config.getString("data.redis.password"));
}
return jedis;
}
@@ -43,6 +43,6 @@ public class RedisConnection implements PlexBase
public final boolean isEnabled()
{
return plugin.config.getBoolean("data.side.enabled");
return plugin.config.getBoolean("data.redis.enabled");
}
}
@@ -1,144 +1,16 @@
package dev.plex.storage;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import dev.plex.Plex;
import dev.plex.PlexBase;
import lombok.Getter;
import dev.plex.storage.database.Database;
import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@Getter
public class SQLConnection implements PlexBase
/**
* Database bootstrap and connection holder.
*
* <p>The historical name is kept so existing module-facing accessors do not break.</p>
*/
public class SQLConnection extends Database
{
private HikariDataSource dataSource;
public SQLConnection()
public java.sql.Connection getCon() throws java.sql.SQLException
{
if (!plugin.config.getString("data.central.storage").equalsIgnoreCase("sqlite") && !plugin.config.getString("data.central.storage").equalsIgnoreCase("mariadb"))
{
return;
}
String host = plugin.config.getString("data.central.hostname");
int port = plugin.config.getInt("data.central.port");
String username = plugin.config.getString("data.central.user");
String password = plugin.config.getString("data.central.password");
String database = plugin.config.getString("data.central.db");
HikariConfig config = new HikariConfig();
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
this.dataSource = new HikariDataSource();
dataSource.setMaxLifetime(15000);
dataSource.setIdleTimeout(15000 * 2);
dataSource.setConnectionTimeout(15000 * 4);
dataSource.setMinimumIdle(2);
dataSource.setMaximumPoolSize(10);
try
{
if (plugin.config.getString("data.central.storage").equalsIgnoreCase("sqlite"))
{
dataSource.setJdbcUrl("jdbc:sqlite:" + new File(plugin.getDataFolder(), "database.db").getAbsolutePath());
plugin.setStorageType(StorageType.SQLITE);
}
else if (plugin.config.getString("data.central.storage").equalsIgnoreCase("mariadb"))
{
Class.forName("org.mariadb.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mariadb://" + host + ":" + port + "/" + database);
dataSource.setUsername(username);
dataSource.setPassword(password);
Plex.get().setStorageType(StorageType.MARIADB);
}
}
catch (ClassNotFoundException throwables)
{
throwables.printStackTrace();
}
try (Connection con = getCon())
{
if (tableExistsSQL("players"))
{
}
con.prepareStatement("CREATE TABLE IF NOT EXISTS `players` (" +
"`uuid` VARCHAR(46) NOT NULL, " +
"`name` VARCHAR(18), " +
"`login_msg` VARCHAR(2000), " +
"`prefix` VARCHAR(2000), " +
"`staffChat` BOOLEAN, " +
"`ips` VARCHAR(2000), " +
"`coins` BIGINT, " +
"`vanished` BOOLEAN, " +
"`commandspy` BOOLEAN, " +
"PRIMARY KEY (`uuid`));").execute();
con.prepareStatement("CREATE TABLE IF NOT EXISTS `punishments` (" +
"`punished` VARCHAR(46) NOT NULL, " +
"`punisher` VARCHAR(46), " +
"`punisherName` VARCHAR(64), " +
"`punishedUsername` VARCHAR(16), " +
"`ip` VARCHAR(2000), " +
"`type` VARCHAR(30), " +
"`reason` VARCHAR(2000), " +
"`customTime` BOOLEAN, " +
"`active` BOOLEAN, " +
"`endDate` BIGINT" +
");").execute();
con.prepareStatement("CREATE TABLE IF NOT EXISTS `notes` (" +
"`id` INT NOT NULL, " +
"`uuid` VARCHAR(46) NOT NULL, " +
"`written_by` VARCHAR(46), " +
"`note` VARCHAR(2000), " +
"`timestamp` BIGINT" +
");").execute();
}
catch (SQLException throwables)
{
throwables.printStackTrace();
}
}
private boolean tableExistsSQL(String tableName) throws SQLException
{
try (Connection connection = getCon())
{
PreparedStatement preparedStatement = connection.prepareStatement("SELECT count(*) "
+ "FROM information_schema.tables "
+ "WHERE table_name = ?"
+ "LIMIT 1;");
preparedStatement.setString(1, tableName);
ResultSet resultSet = preparedStatement.executeQuery();
resultSet.next();
return resultSet.getInt(1) != 0;
}
catch (SQLException ignored)
{
return false;
}
}
public Connection getCon()
{
if (this.dataSource == null)
{
return null;
}
try
{
return dataSource.getConnection();
}
catch (SQLException e)
{
e.printStackTrace();
}
return null;
return getConnection();
}
}
@@ -1,6 +1,109 @@
package dev.plex.storage;
import com.zaxxer.hikari.HikariConfig;
import dev.plex.Plex;
import java.io.File;
import java.util.Arrays;
import java.util.Locale;
import java.util.Set;
public enum StorageType
{
MARIADB, SQLITE
SQLITE("SQLite", "sqlite", "org.sqlite.JDBC", Set.of("sqlite"))
{
@Override
public void configure(HikariConfig config, Plex plugin)
{
File databaseFile = new File(plugin.getDataFolder(), "database.db");
config.setJdbcUrl("jdbc:sqlite:" + databaseFile.getAbsolutePath());
config.setDriverClassName(driverClass);
config.setMaximumPoolSize(1);
}
@Override
public String migrationHistoryTableSql(String tableName)
{
return "CREATE TABLE IF NOT EXISTS " + tableName + " (version VARCHAR(100) NOT NULL PRIMARY KEY, installed_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000))";
}
},
MARIADB("MariaDB", "mariadb", "org.mariadb.jdbc.Driver", Set.of("mariadb", "mysql"))
{
@Override
public void configure(HikariConfig config, Plex plugin)
{
configureRemote(config, plugin, "jdbc:mariadb://", driverClass);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
}
@Override
public String migrationHistoryTableSql(String tableName)
{
return "CREATE TABLE IF NOT EXISTS `" + tableName + "` (`version` VARCHAR(100) NOT NULL PRIMARY KEY, `installed_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)";
}
},
POSTGRES("PostgreSQL", "postgres", "org.postgresql.Driver", Set.of("postgres", "postgresql"))
{
@Override
public void configure(HikariConfig config, Plex plugin)
{
configureRemote(config, plugin, "jdbc:postgresql://", driverClass);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
}
};
private final Set<String> aliases;
private final String displayName;
private final String migrationDirectory;
protected final String driverClass;
StorageType(String displayName, String migrationDirectory, String driverClass, Set<String> aliases)
{
this.aliases = aliases;
this.displayName = displayName;
this.migrationDirectory = migrationDirectory;
this.driverClass = driverClass;
}
public static StorageType fromConfig(String value)
{
String normalized = value == null ? "sqlite" : value.trim().toLowerCase(Locale.ROOT);
return Arrays.stream(values())
.filter(type -> type.aliases.contains(normalized))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unsupported database storage type: " + value));
}
public abstract void configure(HikariConfig config, Plex plugin);
public String migrationHistoryTableSql(String tableName)
{
return "CREATE TABLE IF NOT EXISTS " + tableName + " (version VARCHAR(100) NOT NULL PRIMARY KEY, installed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)";
}
public String getDisplayName()
{
return displayName;
}
public String getMigrationDirectory()
{
return migrationDirectory;
}
private static void configureRemote(HikariConfig config, Plex plugin, String jdbcPrefix, String driverClass)
{
String host = plugin.config.getString("data.db.hostname");
int port = plugin.config.getInt("data.db.port");
String database = plugin.config.getString("data.db.name");
config.setJdbcUrl(jdbcPrefix + host + ":" + port + "/" + database);
config.setDriverClassName(driverClass);
config.setUsername(plugin.config.getString("data.db.user"));
config.setPassword(plugin.config.getString("data.db.password"));
}
}
@@ -1,17 +0,0 @@
package dev.plex.storage.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Taah
* @since 1:42 AM [25-08-2023]
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MapObjectList
{
}
@@ -1,17 +0,0 @@
package dev.plex.storage.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Taah
* @since 1:42 AM [25-08-2023]
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NoLimit
{
}
@@ -1,18 +0,0 @@
package dev.plex.storage.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Taah
* @since 1:42 AM [25-08-2023]
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface PrimaryKey
{
boolean dontSet() default false;
}
@@ -1,18 +0,0 @@
package dev.plex.storage.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Taah
* @since 4:27 AM [25-08-2023]
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SQLTable
{
String value();
}
@@ -1,18 +0,0 @@
package dev.plex.storage.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Taah
* @since 1:42 AM [25-08-2023]
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface VarcharLimit
{
int value();
}
@@ -0,0 +1,173 @@
package dev.plex.storage.database;
import com.j256.ormlite.jdbc.DataSourceConnectionSource;
import com.j256.ormlite.support.ConnectionSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import dev.plex.PlexBase;
import dev.plex.storage.StorageType;
import dev.plex.util.PlexLog;
import lombok.Getter;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
@Getter
public class Database implements PlexBase
{
private static final String MIGRATION_TABLE = "plex_schema_history";
private final HikariDataSource dataSource;
private final ConnectionSource connectionSource;
private final StorageType storageType;
public Database()
{
this.storageType = StorageType.fromConfig(plugin.config.getString("data.db.storage", "sqlite"));
HikariConfig config = new HikariConfig();
config.setPoolName("Plex-Database");
config.setMaxLifetime(1_800_000);
config.setIdleTimeout(600_000);
config.setKeepaliveTime(0);
config.setConnectionTimeout(60_000);
config.setMinimumIdle(2);
config.setMaximumPoolSize(10);
storageType.configure(config, plugin);
plugin.setStorageType(this.storageType);
this.dataSource = new HikariDataSource(config);
try
{
this.connectionSource = new DataSourceConnectionSource(dataSource, config.getJdbcUrl());
runMigrations();
}
catch (Exception e)
{
dataSource.close();
throw new IllegalStateException("Failed to initialize database", e);
}
}
private void runMigrations() throws Exception
{
try (Connection connection = dataSource.getConnection())
{
ensureMigrationTable(connection);
for (String migration : List.of("001_initial_schema"))
{
if (hasMigration(connection, migration))
{
continue;
}
executeMigration(connection, migration);
try (Statement statement = connection.createStatement())
{
statement.executeUpdate("INSERT INTO " + MIGRATION_TABLE + " (version) VALUES ('" + migration + "')");
}
PlexLog.log("Applied database migration " + migration);
}
}
}
private void ensureMigrationTable(Connection connection) throws SQLException
{
try (Statement statement = connection.createStatement())
{
statement.execute(storageType.migrationHistoryTableSql(MIGRATION_TABLE));
}
}
private boolean hasMigration(Connection connection, String migration) throws SQLException
{
try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT version FROM " + MIGRATION_TABLE + " WHERE version = '" + migration + "'"))
{
return resultSet.next();
}
}
private void executeMigration(Connection connection, String migration) throws Exception
{
String resource = "db/migration/" + storageType.getMigrationDirectory() + "/" + migration + ".sql";
try (InputStream stream = getClass().getClassLoader().getResourceAsStream(resource))
{
if (stream == null)
{
throw new IllegalStateException("Missing database migration resource: " + resource);
}
for (String sql : splitStatements(new String(stream.readAllBytes(), StandardCharsets.UTF_8)))
{
try (Statement statement = connection.createStatement())
{
statement.execute(sql);
}
}
}
}
private List<String> splitStatements(String script)
{
List<String> statements = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
for (int i = 0; i < script.length(); i++)
{
char c = script.charAt(i);
if (c == '\'' && !inDoubleQuote)
{
inSingleQuote = !inSingleQuote;
}
else if (c == '"' && !inSingleQuote)
{
inDoubleQuote = !inDoubleQuote;
}
if (c == ';' && !inSingleQuote && !inDoubleQuote)
{
addStatement(statements, current);
current.setLength(0);
continue;
}
current.append(c);
}
addStatement(statements, current);
return statements;
}
private void addStatement(List<String> statements, StringBuilder statement)
{
String sql = statement.toString().replaceAll("(?m)^\\s*--.*$", "").trim();
if (!sql.isEmpty())
{
statements.add(sql);
}
}
public Connection getConnection() throws SQLException
{
return dataSource.getConnection();
}
public void close()
{
try
{
connectionSource.close();
}
catch (Exception e)
{
PlexLog.warn("Failed to close ORMLite connection source: " + e.getMessage());
}
dataSource.close();
}
}
@@ -0,0 +1,34 @@
package dev.plex.storage.database.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@DatabaseTable(tableName = "notes")
public class NoteEntity
{
@DatabaseField(generatedId = true, columnName = "row_id")
private long rowId;
@DatabaseField(columnName = "id", index = true)
private int id;
@DatabaseField(columnName = "uuid", canBeNull = false, index = true, width = 46)
private String uuid;
@DatabaseField(columnName = "written_by", width = 46)
private String writtenBy;
@DatabaseField(columnName = "note", width = 2000)
private String note;
@DatabaseField(columnName = "timestamp")
private long timestamp;
public NoteEntity()
{
}
}
@@ -0,0 +1,43 @@
package dev.plex.storage.database.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@DatabaseTable(tableName = "players")
public class PlayerEntity
{
@DatabaseField(id = true, columnName = "uuid", width = 46)
private String uuid;
@DatabaseField(columnName = "name", width = 18)
private String name;
@DatabaseField(columnName = "login_msg", width = 2000)
private String loginMessage;
@DatabaseField(columnName = "prefix", width = 2000)
private String prefix;
@DatabaseField(columnName = "staffChat")
private boolean staffChat;
@DatabaseField(columnName = "ips", width = 2000)
private String ips;
@DatabaseField(columnName = "coins")
private long coins;
@DatabaseField(columnName = "vanished")
private boolean vanished;
@DatabaseField(columnName = "commandspy")
private boolean commandSpy;
public PlayerEntity()
{
}
}
@@ -0,0 +1,31 @@
package dev.plex.storage.database.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@DatabaseTable(tableName = "player_ips")
public class PlayerIpEntity
{
@DatabaseField(generatedId = true, columnName = "id")
private long id;
@DatabaseField(columnName = "player_uuid", canBeNull = false, index = true, width = 46)
private String playerUuid;
@DatabaseField(columnName = "ip", canBeNull = false, index = true, width = 64)
private String ip;
public PlayerIpEntity()
{
}
public PlayerIpEntity(String playerUuid, String ip)
{
this.playerUuid = playerUuid;
this.ip = ip;
}
}
@@ -0,0 +1,52 @@
package dev.plex.storage.database.entity;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@DatabaseTable(tableName = "punishments")
public class PunishmentEntity
{
@DatabaseField(generatedId = true, columnName = "id")
private long id;
@DatabaseField(columnName = "punished", canBeNull = false, index = true, width = 46)
private String punished;
@DatabaseField(columnName = "punisher", width = 46)
private String punisher;
@DatabaseField(columnName = "punisherName", width = 64)
private String punisherName;
@DatabaseField(columnName = "punishedUsername", width = 16)
private String punishedUsername;
@DatabaseField(columnName = "ip", width = 2000, index = true)
private String ip;
@DatabaseField(columnName = "type", width = 30)
private String type;
@DatabaseField(columnName = "reason", width = 2000)
private String reason;
@DatabaseField(columnName = "customTime")
private boolean customTime;
@DatabaseField(columnName = "active", index = true)
private boolean active;
@DatabaseField(columnName = "issueDate")
private long issueDate;
@DatabaseField(columnName = "endDate")
private long endDate;
public PunishmentEntity()
{
}
}
@@ -2,72 +2,66 @@ package dev.plex.storage.player;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.stmt.DeleteBuilder;
import dev.plex.Plex;
import dev.plex.player.PlexPlayer;
import dev.plex.storage.StorageType;
import dev.plex.util.PlexLog;
import dev.plex.storage.database.entity.PlayerEntity;
import dev.plex.storage.database.entity.PlayerIpEntity;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
/**
* SQL fetching utilities for players
* Player persistence backed by ORMLite.
*/
public class SQLPlayerData
{
private final String SELECT = "SELECT * FROM `players` WHERE uuid=?";
private final String UPDATE = "UPDATE `players` SET name=?, login_msg=?, prefix=?, ips=?, coins=?, vanished=?, commandspy=? WHERE uuid=?";
private final String INSERT = "INSERT INTO `players` (`uuid`, `name`, `login_msg`, `prefix`, `ips`, `coins`, `vanished`, `commandspy`) VALUES (?, ?, ?, ?, ?, ?, ?, ?);";
private static final Gson GSON = new Gson();
private final Dao<PlayerEntity, String> players;
private final Dao<PlayerIpEntity, Long> playerIps;
public SQLPlayerData()
{
try
{
this.players = DaoManager.createDao(Plex.get().getSqlConnection().getConnectionSource(), PlayerEntity.class);
this.playerIps = DaoManager.createDao(Plex.get().getSqlConnection().getConnectionSource(), PlayerIpEntity.class);
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to create player DAOs", e);
}
}
/**
* Checks if a player exists in the SQL database
*
* @param uuid The unique ID of the player
* @return true if the player was found in the database
*/
public boolean exists(UUID uuid)
{
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement(SELECT);
statement.setString(1, uuid.toString());
ResultSet set = statement.executeQuery();
return set.next();
return players.idExists(uuid.toString());
}
catch (SQLException throwables)
catch (SQLException e)
{
throwables.printStackTrace();
e.printStackTrace();
return false;
}
return false;
}
public boolean exists(String username)
{
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement("SELECT * FROM `players` WHERE name=?");
statement.setString(1, username);
ResultSet set = statement.executeQuery();
return set.next();
return players.queryBuilder().where().eq("name", username).queryForFirst() != null;
}
catch (SQLException throwables)
catch (SQLException e)
{
throwables.printStackTrace();
e.printStackTrace();
return false;
}
return false;
}
/**
* Gets the player from cache or from the SQL database
*
* @param uuid The unique ID of the player
* @return a PlexPlayer object
* @see PlexPlayer
*/
public PlexPlayer getByUUID(UUID uuid, boolean loadExtraData)
{
if (Plex.get().getPlayerCache().getPlexPlayerMap().containsKey(uuid))
@@ -75,48 +69,17 @@ public class SQLPlayerData
return Plex.get().getPlayerCache().getPlexPlayerMap().get(uuid);
}
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement(SELECT);
statement.setString(1, uuid.toString());
ResultSet set = statement.executeQuery();
PlexPlayer plexPlayer = new PlexPlayer(uuid, loadExtraData);
while (set.next())
{
String name = set.getString("name");
String loginMSG = set.getString("login_msg");
String prefix = set.getString("prefix");
long coins = set.getLong("coins");
boolean vanished = set.getBoolean("vanished");
boolean commandspy = set.getBoolean("commandspy");
List<String> ips = new Gson().fromJson(set.getString("ips"), new TypeToken<List<String>>()
{
}.getType());
plexPlayer.setName(name);
plexPlayer.setLoginMessage(loginMSG);
plexPlayer.setPrefix(prefix);
plexPlayer.setIps(ips);
plexPlayer.setCoins(coins);
plexPlayer.setVanished(vanished);
plexPlayer.setCommandSpy(commandspy);
}
return plexPlayer;
return toPlayer(players.queryForId(uuid.toString()), loadExtraData);
}
catch (SQLException throwables)
catch (SQLException e)
{
throwables.printStackTrace();
e.printStackTrace();
return null;
}
return null;
}
/**
* Gets the player from cache or from the SQL database
*
* @param uuid The unique ID of the player
* @return a PlexPlayer object
* @see PlexPlayer
*/
public String getNameByUUID(UUID uuid)
{
if (Plex.get().getPlayerCache().getPlexPlayerMap().containsKey(uuid))
@@ -124,67 +87,43 @@ public class SQLPlayerData
return Plex.get().getPlayerCache().getPlexPlayerMap().get(uuid).getName();
}
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement("SELECT `name` FROM `players` WHERE uuid=?");
statement.setString(1, uuid.toString());
ResultSet set = statement.executeQuery();
if (set.next())
{
return set.getString("name");
}
PlayerEntity entity = players.queryForId(uuid.toString());
return entity == null ? null : entity.getName();
}
catch (SQLException throwables)
catch (SQLException e)
{
throwables.printStackTrace();
e.printStackTrace();
return null;
}
return null;
}
public PlexPlayer getByUUID(UUID uuid)
{
return this.getByUUID(uuid, true);
return getByUUID(uuid, true);
}
public PlexPlayer getByName(String username, boolean loadExtraData)
{
PlexPlayer player = Plex.get().getPlayerCache().getPlexPlayerMap().values().stream().filter(plexPlayer -> plexPlayer.getName().equalsIgnoreCase(username)).findFirst().orElse(null);
PlexPlayer player = Plex.get().getPlayerCache().getPlexPlayerMap().values().stream()
.filter(plexPlayer -> plexPlayer.getName().equalsIgnoreCase(username))
.findFirst()
.orElse(null);
if (player != null)
{
return player;
}
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement("SELECT * FROM `players` WHERE name=? LIMIT 1");
statement.setString(1, username);
ResultSet set = statement.executeQuery();
while (set.next())
{
PlexPlayer plexPlayer = new PlexPlayer(UUID.fromString(set.getString("uuid")), loadExtraData);
String loginMSG = set.getString("login_msg");
String prefix = set.getString("prefix");
long coins = set.getLong("coins");
boolean vanished = set.getBoolean("vanished");
boolean commandspy = set.getBoolean("commandspy");
List<String> ips = new Gson().fromJson(set.getString("ips"), new TypeToken<List<String>>()
{
}.getType());
plexPlayer.setName(username);
plexPlayer.setLoginMessage(loginMSG);
plexPlayer.setPrefix(prefix);
plexPlayer.setIps(ips);
plexPlayer.setCoins(coins);
plexPlayer.setVanished(vanished);
plexPlayer.setCommandSpy(commandspy);
return plexPlayer;
}
return toPlayer(players.queryBuilder().limit(1L).where().eq("name", username).queryForFirst(), loadExtraData);
}
catch (SQLException e)
{
e.printStackTrace();
return null;
}
catch (SQLException throwables)
{
throwables.printStackTrace();
}
return null;
}
public PlexPlayer getByName(String username)
@@ -192,155 +131,115 @@ public class SQLPlayerData
return getByName(username, true);
}
/**
* Gets the player from cache or from the SQL database
*
* @param ip The IP address of the player.
* @return a PlexPlayer object
* @see PlexPlayer
*/
public PlexPlayer getByIP(String ip)
{
PlexPlayer player = Plex.get().getPlayerCache().getPlexPlayerMap().values().stream().filter(plexPlayer -> plexPlayer.getIps().contains(ip)).findFirst().orElse(null);
PlexPlayer player = Plex.get().getPlayerCache().getPlexPlayerMap().values().stream()
.filter(plexPlayer -> plexPlayer.getIps().contains(ip))
.findFirst()
.orElse(null);
if (player != null)
{
return player;
}
if (Plex.get().getStorageType() == StorageType.MARIADB)
try
{
try (Connection con = Plex.get().getSqlConnection().getCon())
PlayerIpEntity playerIp = playerIps.queryBuilder().limit(1L).where().eq("ip", ip).queryForFirst();
if (playerIp != null)
{
PreparedStatement statement = con.prepareStatement("select * from `players` where json_search(ips, ?, ?) IS NOT NULL LIMIT 1");
statement.setString(1, "one");
statement.setString(2, ip);
ResultSet set = statement.executeQuery();
PlexPlayer plexPlayer = null;
while (set.next())
{
String uuid = set.getString("uuid");
String name = set.getString("name");
String loginMSG = set.getString("login_msg");
String prefix = set.getString("prefix");
long coins = set.getLong("coins");
boolean vanished = set.getBoolean("vanished");
boolean commandspy = set.getBoolean("commandspy");
List<String> ips = new Gson().fromJson(set.getString("ips"), new TypeToken<List<String>>()
{
}.getType());
plexPlayer = new PlexPlayer(UUID.fromString(uuid));
plexPlayer.setName(name);
plexPlayer.setLoginMessage(loginMSG);
plexPlayer.setPrefix(prefix);
plexPlayer.setIps(ips);
plexPlayer.setCoins(coins);
plexPlayer.setVanished(vanished);
plexPlayer.setCommandSpy(commandspy);
}
return plexPlayer;
return toPlayer(players.queryForId(playerIp.getPlayerUuid()), true);
}
catch (SQLException throwables)
for (PlayerEntity entity : players.queryForAll())
{
throwables.printStackTrace();
List<String> ips = parseIps(entity.getIps());
if (ips.contains(ip))
{
syncIps(entity.getUuid(), ips);
return toPlayer(entity, true);
}
}
}
else if (Plex.get().getStorageType() == StorageType.SQLITE)
catch (SQLException e)
{
PlexLog.warn("Querying a user by IP running SQLite can cause performance issues! Please try to switch to a remote DB ASAP!");
try (Connection con = Plex.get().getSqlConnection().getCon())
{
PreparedStatement statement = con.prepareStatement("select * from `players`");
ResultSet set = statement.executeQuery();
PlexPlayer plexPlayer = null;
while (set.next())
{
List<String> ips = new Gson().fromJson(set.getString("ips"), new TypeToken<List<String>>()
{
}.getType());
if (!ips.contains(ip))
{
continue;
}
String uuid = set.getString("uuid");
String name = set.getString("name");
String loginMSG = set.getString("login_msg");
String prefix = set.getString("prefix");
long coins = set.getLong("coins");
boolean vanished = set.getBoolean("vanished");
boolean commandspy = set.getBoolean("commandspy");
plexPlayer = new PlexPlayer(UUID.fromString(uuid));
plexPlayer.setName(name);
plexPlayer.setLoginMessage(loginMSG);
plexPlayer.setPrefix(prefix);
plexPlayer.setIps(ips);
plexPlayer.setCoins(coins);
plexPlayer.setVanished(vanished);
plexPlayer.setCommandSpy(commandspy);
}
return plexPlayer;
}
catch (SQLException throwables)
{
throwables.printStackTrace();
}
e.printStackTrace();
}
return null;
}
/**
* Updates a player's information in the SQL database
*
* @param player The PlexPlayer object
* @see PlexPlayer
*/
public void update(PlexPlayer player)
{
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement(UPDATE);
statement.setString(1, player.getName());
statement.setString(2, player.getLoginMessage());
statement.setString(3, player.getPrefix());
statement.setString(4, new Gson().toJson(player.getIps()));
statement.setLong(5, player.getCoins());
statement.setBoolean(6, player.isVanished());
statement.setBoolean(7, player.isCommandSpy());
statement.setString(8, player.getUuid().toString());
statement.executeUpdate();
players.createOrUpdate(toEntity(player));
syncIps(player.getUuid().toString(), player.getIps());
}
catch (SQLException throwables)
catch (SQLException e)
{
throwables.printStackTrace();
e.printStackTrace();
}
}
/**
* Inserts the player's information in the database
*
* @param player The PlexPlayer object
* @see PlexPlayer
*/
public void insert(PlexPlayer player)
{
try (Connection con = Plex.get().getSqlConnection().getCon())
update(player);
}
private PlexPlayer toPlayer(PlayerEntity entity, boolean loadExtraData)
{
if (entity == null)
{
PreparedStatement statement = con.prepareStatement(INSERT);
statement.setString(1, player.getUuid().toString());
statement.setString(2, player.getName());
statement.setString(3, player.getLoginMessage());
statement.setString(4, player.getPrefix());
statement.setString(5, new Gson().toJson(player.getIps()));
statement.setLong(6, player.getCoins());
statement.setBoolean(7, player.isVanished());
statement.setBoolean(8, player.isCommandSpy());
statement.execute();
return null;
}
catch (SQLException throwables)
PlexPlayer plexPlayer = new PlexPlayer(UUID.fromString(entity.getUuid()), loadExtraData);
plexPlayer.setName(entity.getName());
plexPlayer.setLoginMessage(entity.getLoginMessage());
plexPlayer.setPrefix(entity.getPrefix());
plexPlayer.setStaffChat(entity.isStaffChat());
plexPlayer.setIps(parseIps(entity.getIps()));
plexPlayer.setCoins(entity.getCoins());
plexPlayer.setVanished(entity.isVanished());
plexPlayer.setCommandSpy(entity.isCommandSpy());
return plexPlayer;
}
private PlayerEntity toEntity(PlexPlayer player)
{
PlayerEntity entity = new PlayerEntity();
entity.setUuid(player.getUuid().toString());
entity.setName(player.getName());
entity.setLoginMessage(player.getLoginMessage());
entity.setPrefix(player.getPrefix());
entity.setStaffChat(player.isStaffChat());
entity.setIps(GSON.toJson(player.getIps()));
entity.setCoins(player.getCoins());
entity.setVanished(player.isVanished());
entity.setCommandSpy(player.isCommandSpy());
return entity;
}
private List<String> parseIps(String ips)
{
if (ips == null || ips.isBlank())
{
throwables.printStackTrace();
return List.of();
}
List<String> parsed = GSON.fromJson(ips, new TypeToken<List<String>>()
{
}.getType());
return parsed == null ? List.of() : parsed;
}
private void syncIps(String playerUuid, List<String> ips) throws SQLException
{
DeleteBuilder<PlayerIpEntity, Long> delete = playerIps.deleteBuilder();
delete.where().eq("player_uuid", playerUuid);
delete.delete();
for (String ip : ips.stream().distinct().toList())
{
playerIps.create(new PlayerIpEntity(playerUuid, ip));
}
}
}
@@ -1,55 +1,55 @@
package dev.plex.storage.punishment;
import com.google.common.collect.Lists;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.stmt.DeleteBuilder;
import dev.plex.Plex;
import dev.plex.punishment.extra.Note;
import dev.plex.storage.database.entity.NoteEntity;
import dev.plex.util.TimeUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class SQLNotes
{
private static final String SELECT = "SELECT * FROM `notes` WHERE uuid=?";
private static final String INSERT = "INSERT INTO `notes` (`id`, `uuid`, `written_by`, `note`, `timestamp`) VALUES(?, ?, ?, ?, ?)";
private static final String DELETE = "DELETE FROM `notes` WHERE uuid=? AND id=?";
private final Dao<NoteEntity, Long> notes;
public SQLNotes()
{
try
{
this.notes = DaoManager.createDao(Plex.get().getSqlConnection().getConnectionSource(), NoteEntity.class);
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to create note DAO", e);
}
}
public CompletableFuture<List<Note>> getNotes(UUID uuid)
{
return CompletableFuture.supplyAsync(() ->
{
List<Note> notes = Lists.newArrayList();
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement(SELECT);
statement.setString(1, uuid.toString());
ResultSet set = statement.executeQuery();
while (set.next())
{
Note note = new Note(
uuid,
set.getString("note"),
UUID.fromString(set.getString("written_by")),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("timestamp")), ZoneId.of(TimeUtils.TIMEZONE))
);
note.setId(set.getInt("id"));
notes.add(note);
}
return notes.queryForEq("uuid", uuid.toString()).stream()
.sorted(Comparator.comparingInt(NoteEntity::getId))
.map(this::toNote)
.toList();
}
catch (SQLException e)
{
e.printStackTrace();
return notes;
return Lists.newArrayList();
}
return notes;
});
}
@@ -57,12 +57,11 @@ public class SQLNotes
{
return CompletableFuture.runAsync(() ->
{
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement(DELETE);
statement.setString(1, uuid.toString());
statement.setInt(2, id);
statement.execute();
DeleteBuilder<NoteEntity, Long> delete = notes.deleteBuilder();
delete.where().eq("uuid", uuid.toString()).and().eq("id", id);
delete.delete();
}
catch (SQLException e)
{
@@ -75,24 +74,44 @@ public class SQLNotes
{
return CompletableFuture.runAsync(() ->
{
getNotes(note.getUuid()).whenComplete((notes, throwable) ->
try
{
try (Connection con = Plex.get().getSqlConnection().getCon())
{
PreparedStatement statement = con.prepareStatement(INSERT);
statement.setInt(1, notes.size() + 1);
statement.setString(2, note.getUuid().toString());
statement.setString(3, note.getWrittenBy().toString());
statement.setString(4, note.getNote());
statement.setLong(5, note.getTimestamp().toInstant().toEpochMilli());
statement.execute();
note.setId(notes.size());
}
catch (SQLException e)
{
e.printStackTrace();
}
});
int nextId = notes.queryForEq("uuid", note.getUuid().toString()).stream()
.map(NoteEntity::getId)
.max(Integer::compareTo)
.orElse(0) + 1;
NoteEntity entity = toEntity(note);
entity.setId(nextId);
notes.create(entity);
note.setId(nextId);
}
catch (SQLException e)
{
e.printStackTrace();
}
});
}
private Note toNote(NoteEntity entity)
{
Note note = new Note(
UUID.fromString(entity.getUuid()),
entity.getNote(),
UUID.fromString(entity.getWrittenBy()),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getTimestamp()), ZoneId.of(TimeUtils.TIMEZONE))
);
note.setId(entity.getId());
return note;
}
private NoteEntity toEntity(Note note)
{
NoteEntity entity = new NoteEntity();
entity.setId(note.getId());
entity.setUuid(note.getUuid().toString());
entity.setWrittenBy(note.getWrittenBy().toString());
entity.setNote(note.getNote());
entity.setTimestamp(note.getTimestamp().toInstant().toEpochMilli());
return entity;
}
}
@@ -1,15 +1,16 @@
package dev.plex.storage.punishment;
import com.google.common.collect.Lists;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.stmt.UpdateBuilder;
import dev.plex.Plex;
import dev.plex.punishment.Punishment;
import dev.plex.punishment.PunishmentType;
import dev.plex.storage.database.entity.PunishmentEntity;
import dev.plex.util.PlexLog;
import dev.plex.util.TimeUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.ZoneId;
@@ -20,122 +21,70 @@ import java.util.concurrent.CompletableFuture;
public class SQLPunishment
{
private static final String SELECT = "SELECT * FROM `punishments` WHERE punished=?";
private static final String SELECT_BY_IP = "SELECT * FROM `punishments` WHERE ip=?";
private static final String SELECT_BY = "SELECT * FROM `punishments` WHERE punisher=?";
private final Dao<PunishmentEntity, Long> punishments;
private static final String INSERT = "INSERT INTO `punishments` (`punished`, `punisher`, `punisherName`, `punishedUsername`, `ip`, `type`, `reason`, `customTime`, `active`, `endDate`) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String UPDATE_PUNISHMENT = "UPDATE `punishments` SET active=? WHERE punished=? AND type=?";
public SQLPunishment()
{
try
{
this.punishments = DaoManager.createDao(Plex.get().getSqlConnection().getConnectionSource(), PunishmentEntity.class);
}
catch (SQLException e)
{
throw new IllegalStateException("Failed to create punishment DAO", e);
}
}
public CompletableFuture<List<Punishment>> getPunishments()
{
return CompletableFuture.supplyAsync(() ->
{
List<Punishment> punishments = Lists.newArrayList();
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement("SELECT * FROM `punishments`");
ResultSet set = statement.executeQuery();
while (set.next())
{
Punishment punishment = new Punishment(UUID.fromString(set.getString("punished")), set.getString("punisher") != null && set.getString("punisher").isEmpty() ? UUID.fromString(set.getString("punisher")) : null);
punishment.setActive(set.getBoolean("active"));
punishment.setType(PunishmentType.valueOf(set.getString("type")));
punishment.setCustomTime(set.getBoolean("customTime"));
punishment.setPunishedUsername(set.getString("punishedUsername"));
punishment.setPunisherName(set.getString("punisherName"));
punishment.setEndDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("endDate")), ZoneId.of(TimeUtils.TIMEZONE)));
punishment.setReason(set.getString("reason"));
punishment.setIp(set.getString("ip"));
punishments.add(punishment);
}
return punishments.queryForAll().stream().map(this::toPunishment).toList();
}
catch (SQLException e)
{
e.printStackTrace();
return punishments;
return Lists.newArrayList();
}
return punishments;
});
}
public List<Punishment> getPunishments(UUID uuid)
{
List<Punishment> punishments = Lists.newArrayList();
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement(SELECT);
statement.setString(1, uuid.toString());
ResultSet set = statement.executeQuery();
while (set.next())
{
Punishment punishment = new Punishment(UUID.fromString(set.getString("punished")), set.getString("punisher") == null ? null : UUID.fromString(set.getString("punisher")));
punishment.setActive(set.getBoolean("active"));
punishment.setType(PunishmentType.valueOf(set.getString("type")));
punishment.setCustomTime(set.getBoolean("customTime"));
punishment.setPunishedUsername(set.getString("punishedUsername"));
punishment.setEndDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("endDate")), ZoneId.of(TimeUtils.TIMEZONE)));
punishment.setReason(set.getString("reason"));
punishment.setIp(set.getString("ip"));
punishments.add(punishment);
}
return punishments.queryForEq("punished", uuid.toString()).stream().map(this::toPunishment).toList();
}
catch (SQLException e)
{
e.printStackTrace();
return Lists.newArrayList();
}
return punishments;
}
public List<Punishment> getPunishments(String ip)
{
List<Punishment> punishments = Lists.newArrayList();
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PreparedStatement statement = con.prepareStatement(SELECT_BY_IP);
statement.setString(1, ip);
ResultSet set = statement.executeQuery();
while (set.next())
{
Punishment punishment = new Punishment(UUID.fromString(set.getString("punished")), set.getString("punisher") == null ? null : UUID.fromString(set.getString("punisher")));
punishment.setActive(set.getBoolean("active"));
punishment.setType(PunishmentType.valueOf(set.getString("type")));
punishment.setCustomTime(set.getBoolean("customTime"));
punishment.setPunishedUsername(set.getString("punishedUsername"));
punishment.setEndDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("endDate")), ZoneId.of(TimeUtils.TIMEZONE)));
punishment.setReason(set.getString("reason"));
punishment.setIp(set.getString("ip"));
punishments.add(punishment);
}
return punishments.queryForEq("ip", ip).stream().map(this::toPunishment).toList();
}
catch (SQLException e)
{
e.printStackTrace();
return Lists.newArrayList();
}
return punishments;
}
public CompletableFuture<Void> insertPunishment(Punishment punishment)
{
return CompletableFuture.runAsync(() ->
{
try (Connection con = Plex.get().getSqlConnection().getCon())
try
{
PlexLog.debug("Running execute punishment on " + punishment.getPunished().toString());
PreparedStatement statement = con.prepareStatement(INSERT);
statement.setString(1, punishment.getPunished().toString());
statement.setString(2, punishment.getPunisher() == null ? null : punishment.getPunisher().toString());
statement.setString(3, punishment.getPunisherName());
statement.setString(4, punishment.getPunishedUsername());
statement.setString(5, punishment.getIp());
statement.setString(6, punishment.getType().name());
statement.setString(7, punishment.getReason());
statement.setBoolean(8, punishment.isCustomTime());
statement.setBoolean(9, punishment.isActive());
statement.setLong(10, punishment.getEndDate().toInstant().toEpochMilli());
PlexLog.debug("Executing punishment");
statement.execute();
PlexLog.debug("Persisting punishment for " + punishment.getPunished());
punishments.create(toEntity(punishment));
}
catch (SQLException e)
{
@@ -146,19 +95,28 @@ public class SQLPunishment
public void syncRemoveBan(UUID uuid)
{
try (Connection con = Plex.get().getSqlConnection().getCon())
{
PreparedStatement statement = con.prepareStatement(UPDATE_PUNISHMENT);
statement.setBoolean(1, false);
statement.setString(2, uuid.toString());
statement.setString(3, PunishmentType.BAN.name());
statement.executeUpdate();
setActive(uuid, PunishmentType.BAN, false);
setActive(uuid, PunishmentType.TEMPBAN, false);
}
PreparedStatement statement1 = con.prepareStatement(UPDATE_PUNISHMENT);
statement1.setBoolean(1, false);
statement1.setString(2, uuid.toString());
statement1.setString(3, PunishmentType.TEMPBAN.name());
statement1.executeUpdate();
public CompletableFuture<Void> updatePunishment(PunishmentType type, boolean active, UUID punished)
{
return CompletableFuture.runAsync(() -> setActive(punished, type, active));
}
public CompletableFuture<Void> removeBan(UUID uuid)
{
return CompletableFuture.runAsync(() -> syncRemoveBan(uuid));
}
private void setActive(UUID punished, PunishmentType type, boolean active)
{
try
{
UpdateBuilder<PunishmentEntity, Long> update = punishments.updateBuilder();
update.updateColumnValue("active", active);
update.where().eq("punished", punished.toString()).and().eq("type", type.name());
update.update();
}
catch (SQLException e)
{
@@ -166,48 +124,36 @@ public class SQLPunishment
}
}
public CompletableFuture<Void> updatePunishment(PunishmentType type, boolean active, UUID punished)
private Punishment toPunishment(PunishmentEntity entity)
{
return CompletableFuture.runAsync(() ->
{
try (Connection con = Plex.get().getSqlConnection().getCon())
{
PreparedStatement statement = con.prepareStatement(UPDATE_PUNISHMENT);
statement.setBoolean(1, active);
statement.setString(2, punished.toString());
statement.setString(3, type.name());
statement.executeUpdate();
}
catch (SQLException e)
{
e.printStackTrace();
}
});
UUID punisher = entity.getPunisher() == null || entity.getPunisher().isBlank() ? null : UUID.fromString(entity.getPunisher());
Punishment punishment = new Punishment(UUID.fromString(entity.getPunished()), punisher);
punishment.setActive(entity.isActive());
punishment.setType(PunishmentType.valueOf(entity.getType()));
punishment.setCustomTime(entity.isCustomTime());
punishment.setPunishedUsername(entity.getPunishedUsername());
punishment.setPunisherName(entity.getPunisherName());
punishment.setIssueDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getIssueDate()), ZoneId.of(TimeUtils.TIMEZONE)));
punishment.setEndDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndDate()), ZoneId.of(TimeUtils.TIMEZONE)));
punishment.setReason(entity.getReason());
punishment.setIp(entity.getIp());
return punishment;
}
public CompletableFuture<Void> removeBan(UUID uuid)
private PunishmentEntity toEntity(Punishment punishment)
{
return CompletableFuture.runAsync(() ->
{
try (Connection con = Plex.get().getSqlConnection().getCon())
{
PreparedStatement statement = con.prepareStatement(UPDATE_PUNISHMENT);
statement.setBoolean(1, false);
statement.setString(2, uuid.toString());
statement.setString(3, PunishmentType.BAN.name());
statement.executeUpdate();
PreparedStatement statement1 = con.prepareStatement(UPDATE_PUNISHMENT);
statement1.setBoolean(1, false);
statement1.setString(2, uuid.toString());
statement1.setString(3, PunishmentType.TEMPBAN.name());
statement1.executeUpdate();
}
catch (SQLException e)
{
e.printStackTrace();
}
});
PunishmentEntity entity = new PunishmentEntity();
entity.setPunished(punishment.getPunished().toString());
entity.setPunisher(punishment.getPunisher() == null ? null : punishment.getPunisher().toString());
entity.setPunisherName(punishment.getPunisherName());
entity.setPunishedUsername(punishment.getPunishedUsername());
entity.setIp(punishment.getIp());
entity.setType(punishment.getType().name());
entity.setReason(punishment.getReason());
entity.setCustomTime(punishment.isCustomTime());
entity.setActive(punishment.isActive());
entity.setIssueDate(punishment.getIssueDate().toInstant().toEpochMilli());
entity.setEndDate(punishment.getEndDate().toInstant().toEpochMilli());
return entity;
}
}
@@ -5,7 +5,6 @@ import com.google.common.collect.Lists;
import dev.plex.Plex;
import dev.plex.PlexBase;
import dev.plex.listener.impl.ChatListener;
import dev.plex.storage.StorageType;
import dev.plex.util.minimessage.SafeMiniMessage;
import java.sql.Connection;
@@ -85,14 +84,7 @@ public class PlexUtils implements PlexBase
{
try (Connection ignored = Plex.get().getSqlConnection().getCon())
{
if (Plex.get().getStorageType() == StorageType.MARIADB)
{
PlexLog.log("Successfully enabled MySQL!");
}
else if (Plex.get().getStorageType() == StorageType.SQLITE)
{
PlexLog.log("Successfully enabled SQLite!");
}
PlexLog.log("Successfully enabled " + Plex.get().getStorageType().getDisplayName() + "!");
}
catch (SQLException e)
{
@@ -1,204 +0,0 @@
package dev.plex.util.sql;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import dev.plex.punishment.PunishmentType;
import dev.plex.storage.annotation.MapObjectList;
import dev.plex.storage.annotation.NoLimit;
import dev.plex.storage.annotation.PrimaryKey;
import dev.plex.storage.annotation.SQLTable;
import dev.plex.storage.annotation.VarcharLimit;
import dev.plex.util.PlexLog;
import dev.plex.util.ReflectionsUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
/**
* @author Taah
* @since 4:28 AM [25-08-2023]
*/
public class SQLUtil
{
public static final Map<String, Table> TABLES = Maps.newHashMap();
public static List<String> createTable(List<String> result, Class<?> clazz)
{
if (!clazz.isAnnotationPresent(SQLTable.class))
{
PlexLog.error("Unable to map {0} to a table, it is missing the SQLTable's annotation", clazz.getName());
return null;
}
final List<Field> collectionFields = Lists.newArrayList();
final Table table = new Table(clazz.getAnnotation(SQLTable.class).value());
final StringBuilder mainResult = new StringBuilder("CREATE TABLE IF NOT EXISTS `" + table.name() + "` (");
final List<Field> declaredFields = Arrays.stream(clazz.getDeclaredFields()).filter(field -> !Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())).collect(Collectors.toList());
final List<Field> iterating = declaredFields.stream().toList();
for (Field value : iterating)
{
if (Collection.class.isAssignableFrom(value.getType()))
{
collectionFields.add(value);
declaredFields.remove(value);
}
}
Field primaryKey = null;
for (int i = 0; i < declaredFields.size(); i++)
{
Field declaredField = declaredFields.get(i);
final Mapper mapped = Mapper.getByClass(declaredField.getType());
if (mapped == null)
{
PlexLog.warn("Could not map field {0} for class {1}", declaredField.getName(), clazz.getName());
continue;
}
if (declaredField.isAnnotationPresent(PrimaryKey.class))
{
if (primaryKey != null)
{
PlexLog.error("You can only have one primary key for a table! The class {0} has more than one!", clazz.getName());
return ImmutableList.of();
}
primaryKey = declaredField;
}
writeFieldToSQL(table, mainResult, mapped, declaredField);
if (i < declaredFields.size() - 1)
{
mainResult.append(", ");
}
}
if (primaryKey != null && !primaryKey.getAnnotation(PrimaryKey.class).dontSet())
{
mainResult.append(", PRIMARY KEY (`").append(primaryKey.getName()).append("`)");
}
mainResult.append(");");
result.add(mainResult.toString());
TABLES.put(table.name(), table);
if (primaryKey == null && !collectionFields.isEmpty())
{
PlexLog.error("You must define a primary key to point to if you wish to have a list saved. You can use @PrimaryKey(dontSet = true) to make sure that SQL does not save it as a primary key.");
return ImmutableList.of();
}
Field finalPrimaryKey = primaryKey;
collectionFields.forEach(field ->
{
final String tableName = field.getName() + "To" + StringUtils.capitalize(clazz.getSimpleName());
StringBuilder sql = new StringBuilder("CREATE TABLE IF NOT EXISTS `" + tableName + "` (");
if (field.isAnnotationPresent(MapObjectList.class))
{
createTable(result, ReflectionsUtil.getGenericField(field));
return;
}
final Mapper mapped = Mapper.getByClass(ReflectionsUtil.getGenericField(field));
if (mapped == null)
{
PlexLog.warn("Could not map collection field {0} for class {1}", field.getName(), clazz.getName());
return;
}
final Table listTable = new Table(tableName);
writeFieldToSQL(listTable, sql, mapped, field);
sql.append(", ");
writeFieldToSQL(listTable, sql, Mapper.getByClass(finalPrimaryKey.getType()), finalPrimaryKey);
sql.append(");");
result.add(sql.toString());
table.mappedTables().put(field, listTable);
});
return result;
}
public static void update(String tableName, Object object)
{
final Table table = TABLES.get(tableName);
if (table == null)
{
PlexLog.error("Table {0} was not found", tableName);
return;
}
}
private static void writeFieldToSQL(Table table, StringBuilder sb, Mapper mapped, Field field)
{
sb.append("`").append(field.getName()).append("` ");
if (mapped == Mapper.VARCHAR)
{
if (field.isAnnotationPresent(NoLimit.class))
{
sb.append("TEXT");
table.columns().put(field.getName(), Mapper.TEXT);
}
else
{
sb.append(mapped.name());
table.columns().put(field.getName(), mapped);
}
}
else
{
sb.append(mapped.name());
table.columns().put(field.getName(), mapped);
}
if (mapped == Mapper.VARCHAR && !field.isAnnotationPresent(NoLimit.class))
{
if (UUID.class.isAssignableFrom(field.getType()))
{
sb.append(" (").append(36).append(")");
}
else if (field.isAnnotationPresent(VarcharLimit.class))
{
int limit = field.getAnnotation(VarcharLimit.class).value();
sb.append(" (").append(limit).append(")");
}
else
{
sb.append("(65535)");
}
}
if (field.isAnnotationPresent(NotNull.class))
{
sb.append(" NOT NULL");
}
}
@Accessors(fluent = true)
public enum Mapper
{
VARCHAR(String.class, UUID.class, PunishmentType.class),
BOOLEAN(Boolean.class, boolean.class),
BIGINT(Long.class, long.class, ZonedDateTime.class),
INT(Integer.class, int.class),
TEXT;
private final Class<?>[] clazz;
Mapper(Class<?>... clazz)
{
this.clazz = clazz;
}
public static Mapper getByClass(Class<?> clazz)
{
return Arrays.stream(values()).filter(mapper -> mapper.clazz != null && Arrays.asList(mapper.clazz).contains(clazz)).findFirst().orElse(null);
}
}
}
@@ -1,23 +0,0 @@
package dev.plex.util.sql;
import com.google.common.collect.Maps;
import java.lang.reflect.Field;
import java.util.Map;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Taah
* @since 5:30 AM [26-08-2023]
*/
@Data
@Accessors(fluent = true)
public class Table
{
private final String name;
private final Map<String, SQLUtil.Mapper> columns = Maps.newHashMap();
private final Map<Field, Table> mappedTables = Maps.newHashMap();
}
+5 -5
View File
@@ -41,14 +41,14 @@ loginmessages:
name: true
data:
central:
storage: sqlite # Use mariadb, or sqlite here
db:
storage: sqlite # Use mariadb, postgres, or sqlite here
user: ""
password: ""
hostname: 127.0.0.1
port: 27017
db: "plex"
side: # This is Redis, leave password blank if auth is false
port: 3306
name: "plex"
redis: # Leave password blank if auth is false
enabled: false
auth: true
hostname: 127.0.0.1
@@ -0,0 +1,51 @@
CREATE TABLE IF NOT EXISTS `players` (
`uuid` VARCHAR(46) NOT NULL,
`name` VARCHAR(18),
`login_msg` VARCHAR(2000),
`prefix` VARCHAR(2000),
`staffChat` BOOLEAN,
`ips` VARCHAR(2000),
`coins` BIGINT,
`vanished` BOOLEAN,
`commandspy` BOOLEAN,
PRIMARY KEY (`uuid`),
INDEX `idx_players_name` (`name`)
);
CREATE TABLE IF NOT EXISTS `punishments` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`punished` VARCHAR(46) NOT NULL,
`punisher` VARCHAR(46),
`punisherName` VARCHAR(64),
`punishedUsername` VARCHAR(16),
`ip` VARCHAR(2000),
`type` VARCHAR(30),
`reason` VARCHAR(2000),
`customTime` BOOLEAN,
`active` BOOLEAN,
`issueDate` BIGINT NOT NULL,
`endDate` BIGINT,
PRIMARY KEY (`id`),
INDEX `idx_punishments_punished` (`punished`),
INDEX `idx_punishments_ip` (`ip`(64))
);
CREATE TABLE IF NOT EXISTS `notes` (
`row_id` BIGINT NOT NULL AUTO_INCREMENT,
`id` INT NOT NULL,
`uuid` VARCHAR(46) NOT NULL,
`written_by` VARCHAR(46),
`note` VARCHAR(2000),
`timestamp` BIGINT,
PRIMARY KEY (`row_id`),
INDEX `idx_notes_uuid` (`uuid`)
);
CREATE TABLE IF NOT EXISTS `player_ips` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`player_uuid` VARCHAR(46) NOT NULL,
`ip` VARCHAR(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_player_ips_player_ip` (`player_uuid`, `ip`),
INDEX `idx_player_ips_ip` (`ip`)
);
@@ -0,0 +1,51 @@
CREATE TABLE IF NOT EXISTS players (
uuid VARCHAR(46) NOT NULL PRIMARY KEY,
name VARCHAR(18),
login_msg VARCHAR(2000),
prefix VARCHAR(2000),
staffChat BOOLEAN,
ips VARCHAR(2000),
coins BIGINT,
vanished BOOLEAN,
commandspy BOOLEAN
);
CREATE INDEX IF NOT EXISTS idx_players_name ON players(name);
CREATE TABLE IF NOT EXISTS punishments (
id BIGSERIAL PRIMARY KEY,
punished VARCHAR(46) NOT NULL,
punisher VARCHAR(46),
punisherName VARCHAR(64),
punishedUsername VARCHAR(16),
ip VARCHAR(2000),
type VARCHAR(30),
reason VARCHAR(2000),
customTime BOOLEAN,
active BOOLEAN,
issueDate BIGINT NOT NULL,
endDate BIGINT
);
CREATE INDEX IF NOT EXISTS idx_punishments_punished ON punishments(punished);
CREATE INDEX IF NOT EXISTS idx_punishments_ip ON punishments(ip);
CREATE TABLE IF NOT EXISTS notes (
row_id BIGSERIAL PRIMARY KEY,
id INT NOT NULL,
uuid VARCHAR(46) NOT NULL,
written_by VARCHAR(46),
note VARCHAR(2000),
timestamp BIGINT
);
CREATE INDEX IF NOT EXISTS idx_notes_uuid ON notes(uuid);
CREATE TABLE IF NOT EXISTS player_ips (
id BIGSERIAL PRIMARY KEY,
player_uuid VARCHAR(46) NOT NULL,
ip VARCHAR(64) NOT NULL,
CONSTRAINT uq_player_ips_player_ip UNIQUE (player_uuid, ip)
);
CREATE INDEX IF NOT EXISTS idx_player_ips_ip ON player_ips(ip);
@@ -0,0 +1,48 @@
CREATE TABLE IF NOT EXISTS players (
uuid VARCHAR(46) NOT NULL PRIMARY KEY,
name VARCHAR(18),
login_msg VARCHAR(2000),
prefix VARCHAR(2000),
staffChat BOOLEAN,
ips VARCHAR(2000),
coins BIGINT,
vanished BOOLEAN,
commandspy BOOLEAN
);
CREATE TABLE IF NOT EXISTS punishments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
punished VARCHAR(46) NOT NULL,
punisher VARCHAR(46),
punisherName VARCHAR(64),
punishedUsername VARCHAR(16),
ip VARCHAR(2000),
type VARCHAR(30),
reason VARCHAR(2000),
customTime BOOLEAN,
active BOOLEAN,
issueDate BIGINT NOT NULL,
endDate BIGINT
);
CREATE TABLE IF NOT EXISTS notes (
row_id INTEGER PRIMARY KEY AUTOINCREMENT,
id INT NOT NULL,
uuid VARCHAR(46) NOT NULL,
written_by VARCHAR(46),
note VARCHAR(2000),
timestamp BIGINT
);
CREATE TABLE IF NOT EXISTS player_ips (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_uuid VARCHAR(46) NOT NULL,
ip VARCHAR(64) NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_players_name ON players(name);
CREATE INDEX IF NOT EXISTS idx_punishments_punished ON punishments(punished);
CREATE INDEX IF NOT EXISTS idx_punishments_ip ON punishments(ip);
CREATE INDEX IF NOT EXISTS idx_notes_uuid ON notes(uuid);
CREATE UNIQUE INDEX IF NOT EXISTS uq_player_ips_player_ip ON player_ips(player_uuid, ip);
CREATE INDEX IF NOT EXISTS idx_player_ips_ip ON player_ips(ip);