New database API

This commit is contained in:
2026-05-24 22:37:32 -04:00
parent 2b9f7c7de7
commit 3c72688e0e
53 changed files with 1398 additions and 265 deletions
+7
View File
@@ -13,6 +13,7 @@ import dev.plex.hook.CoreProtectHook;
import dev.plex.hook.PrismHook;
import dev.plex.hook.RollbackManager;
import dev.plex.module.ModuleManager;
import dev.plex.player.PlayerNameResolver;
import dev.plex.player.PlayerService;
import dev.plex.player.PlexPlayer;
import dev.plex.punishment.PunishmentManager;
@@ -21,6 +22,8 @@ import dev.plex.storage.RedisConnection;
import dev.plex.storage.SQLConnection;
import dev.plex.storage.StorageType;
import dev.plex.storage.player.SQLPlayerData;
import dev.plex.storage.player.PlayerModuleDataRepository;
import dev.plex.storage.player.SQLPlayerModuleData;
import dev.plex.storage.punishment.SQLNotes;
import dev.plex.storage.punishment.SQLPunishment;
import dev.plex.storage.repository.NoteRepository;
@@ -65,7 +68,9 @@ public class Plex extends JavaPlugin
private PlayerCache playerCache;
private PlayerRepository playerRepository;
private PlayerModuleDataRepository playerModuleDataRepository;
private PlayerService playerService;
private PlayerNameResolver playerNameResolver;
private PunishmentRepository punishmentRepository;
private NoteRepository noteRepository;
@@ -217,8 +222,10 @@ public class Plex extends JavaPlugin
punishmentRepository = new SQLPunishment(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor());
playerRepository = new SQLPlayerData(sqlConnection.getConnectionSource(), punishmentRepository);
playerModuleDataRepository = new SQLPlayerModuleData(sqlConnection, storageType);
noteRepository = new SQLNotes(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor());
playerService = new PlayerService(playerCache, playerRepository);
playerNameResolver = new PlayerNameResolver(playerService);
new ListenerHandler(this);
commandHandler = new CommandHandler(this);
@@ -0,0 +1,100 @@
package dev.plex.api.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import dev.plex.api.player.PlayerModuleData;
import dev.plex.storage.player.PlayerModuleDataRepository;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.regex.Pattern;
public class DefaultPlayerModuleData implements PlayerModuleData
{
private static final Gson GSON = new Gson();
private static final Pattern KEY_PATTERN = Pattern.compile("^[a-z][a-z0-9_]{0,63}$");
private final PlayerModuleDataRepository repository;
private final String modulePrefix;
private final UUID playerUuid;
public DefaultPlayerModuleData(PlayerModuleDataRepository repository, String modulePrefix, UUID playerUuid)
{
this.repository = repository;
this.modulePrefix = modulePrefix;
this.playerUuid = playerUuid;
}
@Override
public Optional<JsonElement> get(String key)
{
return repository.get(playerUuid, modulePrefix, validateKey(key));
}
@Override
public <T> Optional<T> get(String key, Class<T> type)
{
return get(key).map(element -> GSON.fromJson(element, type));
}
@Override
public String getString(String key, String fallback)
{
return get(key)
.filter(JsonElement::isJsonPrimitive)
.map(JsonElement::getAsJsonPrimitive)
.filter(primitive -> primitive.isString())
.map(primitive -> primitive.getAsString())
.orElse(fallback);
}
@Override
public long getLong(String key, long fallback)
{
return get(key)
.filter(JsonElement::isJsonPrimitive)
.map(JsonElement::getAsJsonPrimitive)
.filter(primitive -> primitive.isNumber())
.map(primitive -> primitive.getAsLong())
.orElse(fallback);
}
@Override
public boolean getBoolean(String key, boolean fallback)
{
return get(key)
.filter(JsonElement::isJsonPrimitive)
.map(JsonElement::getAsJsonPrimitive)
.filter(primitive -> primitive.isBoolean())
.map(primitive -> primitive.getAsBoolean())
.orElse(fallback);
}
@Override
public void set(String key, JsonElement value)
{
repository.set(playerUuid, modulePrefix, validateKey(key), Objects.requireNonNull(value, "value"));
}
@Override
public void set(String key, Object value)
{
set(key, GSON.toJsonTree(value));
}
@Override
public void remove(String key)
{
repository.remove(playerUuid, modulePrefix, validateKey(key));
}
private String validateKey(String key)
{
if (key == null || !KEY_PATTERN.matcher(key).matches())
{
throw new IllegalArgumentException("Invalid player module data key: " + key);
}
return key;
}
}
@@ -1,9 +1,12 @@
package dev.plex.api.impl;
import dev.plex.Plex;
import dev.plex.api.player.PlayerModuleData;
import dev.plex.api.player.PlayersApi;
import dev.plex.api.player.PlexPlayerView;
import dev.plex.module.PlexModule;
import dev.plex.player.PlexPlayer;
import dev.plex.storage.module.ModuleNames;
import dev.plex.util.PlexUtils;
import java.util.List;
import java.util.Optional;
@@ -15,9 +18,10 @@ final class DefaultPlayersApi implements PlayersApi
DefaultPlayersApi(Plex plugin) { this.plugin = plugin; }
@Override public Optional<? extends PlexPlayerView> byUuid(UUID uuid) { return Optional.ofNullable(plugin.getPlayerService().getPlayer(uuid)).map(DefaultPlexPlayerView::new); }
@Override public Optional<? extends PlexPlayerView> byName(String name) { return Optional.ofNullable(plugin.getPlayerService().getPlayer(name)).map(DefaultPlexPlayerView::new); }
@Override public Optional<? extends PlexPlayerView> player(UUID uuid) { return Optional.ofNullable(plugin.getPlayerService().getPlayer(uuid)).map(player -> new DefaultPlexPlayerView(player, plugin.getPlayerNameResolver())); }
@Override public Optional<? extends PlexPlayerView> byName(String name) { return Optional.ofNullable(plugin.getPlayerService().getPlayer(name)).map(player -> new DefaultPlexPlayerView(player, plugin.getPlayerNameResolver())); }
@Override public List<String> onlineNames() { return PlexUtils.getPlayerNameList(); }
@Override public PlayerModuleData moduleData(PlexModule module, UUID playerUuid) { return new DefaultPlayerModuleData(plugin.getPlayerModuleDataRepository(), ModuleNames.prefix(module), playerUuid); }
static PlexPlayer unwrap(PlexPlayerView view)
{
@@ -2,17 +2,18 @@ package dev.plex.api.impl;
import dev.plex.api.player.PlexPlayerView;
import dev.plex.api.punishment.PunishmentView;
import dev.plex.player.PlayerNameResolver;
import dev.plex.player.PlexPlayer;
import java.util.List;
import java.util.UUID;
import org.bukkit.entity.Player;
record DefaultPlexPlayerView(PlexPlayer player) implements PlexPlayerView
record DefaultPlexPlayerView(PlexPlayer player, PlayerNameResolver playerNameResolver) implements PlexPlayerView
{
@Override public UUID uuid() { return player.getUuid(); }
@Override public String name() { return player.getName(); }
@Override public List<String> ips() { return List.copyOf(player.getIps()); }
@Override public List<? extends PunishmentView> punishments() { return player.getPunishments().stream().map(DefaultPunishmentView::new).toList(); }
@Override public List<? extends PunishmentView> punishments() { return player.getPunishments().stream().map(punishment -> new DefaultPunishmentView(punishment, playerNameResolver)).toList(); }
@Override public boolean frozen() { return player.isFrozen(); }
@Override public boolean muted() { return player.isMuted(); }
@Override public boolean lockedUp() { return player.isLockedUp(); }
@@ -1,18 +1,21 @@
package dev.plex.api.impl;
import dev.plex.api.punishment.PunishmentSource;
import dev.plex.api.punishment.PunishmentType;
import dev.plex.api.punishment.PunishmentView;
import dev.plex.player.PlayerNameResolver;
import dev.plex.punishment.Punishment;
import java.time.ZonedDateTime;
import java.util.UUID;
record DefaultPunishmentView(Punishment punishment) implements PunishmentView
record DefaultPunishmentView(Punishment punishment, PlayerNameResolver playerNameResolver) implements PunishmentView
{
@Override public UUID punished() { return punishment.getPunished(); }
@Override public UUID punisher() { return punishment.getPunisher(); }
@Override public String punisherName() { return punishment.getPunisherName(); }
@Override public PunishmentSource source() { return punishment.getSource() == null ? (punishment.getPunisher() == null ? PunishmentSource.CONSOLE : PunishmentSource.PLAYER) : punishment.getSource(); }
@Override public String punisherReference() { return punishment.getPunisherReference(); }
@Override public String punisherDisplayName() { return Punishment.punisherDisplayName(punishment, playerNameResolver); }
@Override public String ip() { return punishment.getIp(); }
@Override public String punishedUsername() { return punishment.getPunishedUsername(); }
@Override public PunishmentType type() { return PunishmentType.valueOf(punishment.getType().name()); }
@Override public String reason() { return punishment.getReason(); }
@Override public boolean customTime() { return punishment.isCustomTime(); }
@@ -27,9 +27,9 @@ final class DefaultPunishmentsApi implements PunishmentsApi
PlexPlayer player = DefaultPlayersApi.unwrap(playerView);
if (player == null) player = plugin.getPlayerService().getPlayer(playerView.uuid());
Punishment punishment = new Punishment(request.punished(), request.punisher());
punishment.setPunisherName(request.punisherName());
punishment.setSource(request.source());
punishment.setPunisherReference(request.punisherReference());
punishment.setIp(request.ip());
punishment.setPunishedUsername(request.punishedUsername());
punishment.setType(PunishmentType.valueOf(request.type().name()));
punishment.setReason(request.reason());
punishment.setCustomTime(request.customTime());
@@ -1,7 +1,11 @@
package dev.plex.api.impl;
import dev.plex.Plex;
import dev.plex.api.storage.ModuleStorage;
import dev.plex.api.storage.SqlDialect;
import dev.plex.api.storage.StorageApi;
import dev.plex.module.PlexModule;
import dev.plex.storage.module.ServerModuleStorage;
import java.sql.Connection;
import java.sql.SQLException;
@@ -19,4 +23,16 @@ final class DefaultStorageApi implements StorageApi
return function.apply(connection);
}
}
@Override
public ModuleStorage forModule(PlexModule module)
{
return new ServerModuleStorage(plugin, module);
}
@Override
public SqlDialect dialect()
{
return plugin.getStorageType().dialect();
}
}
@@ -97,7 +97,6 @@ public class BanCMD extends ServerCommand
{
punishment.setReason(context.messageString("noReasonProvided"));
}
punishment.setPunishedUsername(plexPlayer.getName());
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
punishment.setEndDate(date.plusDays(1));
punishment.setCustomTime(false);
@@ -107,7 +106,7 @@ public class BanCMD extends ServerCommand
PlexUtils.broadcast(context.messageComponent("banningPlayer", sender.getName(), plexPlayer.getName()));
if (player != null)
{
plugin.getApi().scheduler().runEntity(player, () -> BungeeUtil.kickPlayer(plugin, player, Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerService())));
plugin.getApi().scheduler().runEntity(player, () -> BungeeUtil.kickPlayer(plugin, player, Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerNameResolver())));
}
PlexLog.debug("(From /ban command) PunishedPlayer UUID: " + plexPlayer.getUuid());
@@ -3,9 +3,6 @@ package dev.plex.command.impl;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import dev.plex.command.ServerCommand;
import dev.plex.command.ServerCommandContext;
import dev.plex.punishment.Punishment;
import java.util.stream.Collectors;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
@@ -44,7 +41,7 @@ public class BanListCommand extends ServerCommand
{
plugin.getPunishmentManager().getActiveBans().whenComplete((punishments, throwable) ->
{
context.send(sender, context.messageComponent("activeBansList", punishments.size(), StringUtils.join(punishments.stream().map(Punishment::getPunishedUsername).collect(Collectors.toList()), ", ")));
context.send(sender, context.messageComponent("activeBansList", punishments.size(), StringUtils.join(punishments.stream().map(punishment -> plugin.getPlayerNameResolver().resolve(punishment.getPunished())).toList(), ", ")));
});
return null;
}
@@ -60,7 +60,6 @@ public class FreezeCMD extends ServerCommand
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
punishment.setEndDate(date.plusSeconds(plugin.config.getInt("punishments.freeze-timer", 300)));
punishment.setType(PunishmentType.FREEZE);
punishment.setPunishedUsername(player.getName());
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
punishment.setReason("");
punishment.setActive(true);
@@ -76,14 +76,13 @@ public class KickCMD extends ServerCommand
}
punishment.setReason(reason);
punishment.setPunishedUsername(plexPlayer.getName());
punishment.setEndDate(ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE)));
punishment.setCustomTime(false);
punishment.setActive(false);
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
plugin.getPunishmentManager().punish(plexPlayer, punishment);
PlexUtils.broadcast(context.messageComponent("kickedPlayer", sender.getName(), plexPlayer.getName()));
BungeeUtil.kickPlayer(plugin, player, Punishment.generateKickMessage(punishment, plugin.getPlayerService()));
BungeeUtil.kickPlayer(plugin, player, Punishment.generateKickMessage(punishment, plugin.getPlayerNameResolver()));
return null;
}
@@ -66,7 +66,6 @@ public class MuteCMD extends ServerCommand
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
punishment.setEndDate(date.plusSeconds(plugin.config.getInt("punishments.mute-timer", 300)));
punishment.setType(PunishmentType.MUTE);
punishment.setPunishedUsername(player.getName());
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
punishment.setReason("");
punishment.setActive(true);
@@ -150,7 +150,8 @@ public class NotesCMD extends ServerCommand
AtomicReference<Component> noteList = new AtomicReference<>(context.messageComponent("notesHeader", plexPlayer.getName()));
for (Note note : notes)
{
Component noteLine = context.messageComponent("notePrefix", note.getId(), plugin.getPlayerService().getPlayer(note.getWrittenBy()).getName(), TimeUtils.useTimezone(note.getTimestamp()));
String author = plugin.getPlayerNameResolver().resolve(note.getWrittenBy());
Component noteLine = context.messageComponent("notePrefix", note.getId(), author, TimeUtils.useTimezone(note.getTimestamp()));
noteLine = noteLine.append(context.messageComponent("noteLine", note.getNote()));
noteList.set(noteList.get().append(Component.newline()));
noteList.set(noteList.get().append(noteLine));
@@ -128,7 +128,6 @@ public class SmiteCMD extends ServerCommand
punishment.setCustomTime(false);
punishment.setEndDate(ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE)));
punishment.setType(PunishmentType.SMITE);
punishment.setPunishedUsername(player.getName());
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
if (reason != null)
@@ -83,7 +83,6 @@ public class TempbanCMD extends ServerCommand
{
punishment.setReason(context.messageString("noReasonProvided"));
}
punishment.setPunishedUsername(target.getName());
punishment.setEndDate(TimeUtils.createDate(args[1]));
punishment.setCustomTime(false);
punishment.setActive(true);
@@ -92,7 +91,7 @@ public class TempbanCMD extends ServerCommand
PlexUtils.broadcast(context.messageComponent("banningPlayer", sender.getName(), target.getName()));
if (player != null)
{
plugin.getApi().scheduler().runEntity(player, () -> BungeeUtil.kickPlayer(plugin, player, Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerService())));
plugin.getApi().scheduler().runEntity(player, () -> BungeeUtil.kickPlayer(plugin, player, Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerNameResolver())));
}
if (rollBack)
{
@@ -93,7 +93,6 @@ public class TempmuteCMD extends ServerCommand
punishment.setCustomTime(true);
punishment.setEndDate(endDate);
punishment.setType(PunishmentType.MUTE);
punishment.setPunishedUsername(player.getName());
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
punishment.setReason(reason);
punishment.setActive(true);
@@ -54,7 +54,7 @@ public class BanListener extends ServerListenerBase
PlexPlayer player = plugin.getPlayerService().getPlayer(event.getUniqueId());
player.getPunishments().stream().filter(punishment -> (punishment.getType() == PunishmentType.BAN || punishment.getType() == PunishmentType.TEMPBAN) && punishment.isActive()).findFirst().ifPresent(punishment ->
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED,
Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerService())));
Punishment.generateBanMessage(punishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerNameResolver())));
return;
}
Punishment ipBannedPunishment = plugin.getPunishmentManager().getBanByIP(event.getAddress().getHostAddress());
@@ -66,7 +66,7 @@ public class BanListener extends ServerListenerBase
return;
}
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED,
Punishment.generateBanMessage(ipBannedPunishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerService()));
Punishment.generateBanMessage(ipBannedPunishment, plugin.config.getString("banning.ban_url"), plugin.getPlayerNameResolver()));
}
}
}
}
@@ -54,7 +54,7 @@ public class PlayerListener extends ServerListenerBase
}
if (!plexPlayer.getName().equals(player.getName()))
{
PlexLog.log(plexPlayer.getName() + " has a new name. Changing it to " + player.getName());
PlexLog.log(plexPlayer.getName() + " has a new last known name. Changing it to " + player.getName());
plexPlayer.setName(player.getName());
plugin.getPlayerService().update(plexPlayer);
}
@@ -0,0 +1,96 @@
package dev.plex.player;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class PlayerNameResolver
{
private final PlayerService playerService;
private final HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.build();
private final Map<UUID, String> profileCache = new ConcurrentHashMap<>();
public PlayerNameResolver(PlayerService playerService)
{
this.playerService = playerService;
}
public String resolve(UUID uuid)
{
if (uuid == null)
{
return "CONSOLE";
}
Player online = Bukkit.getPlayer(uuid);
if (online != null)
{
return online.getName();
}
String local = playerService.getNameByUUID(uuid);
if (local != null && !local.isBlank())
{
return local;
}
String cached = profileCache.get(uuid);
if (cached != null && !cached.isBlank())
{
return cached;
}
return lookupMojangName(uuid)
.map(name ->
{
profileCache.put(uuid, name);
return name;
})
.orElse(uuid.toString());
}
private Optional<String> lookupMojangName(UUID uuid)
{
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid.toString().replace("-", "")))
.timeout(Duration.ofSeconds(5))
.GET()
.build();
try
{
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200)
{
return Optional.empty();
}
JsonObject object = JsonParser.parseString(response.body()).getAsJsonObject();
if (!object.has("name") || !object.get("name").isJsonPrimitive())
{
return Optional.empty();
}
return Optional.ofNullable(object.get("name").getAsString()).filter(name -> !name.isBlank());
}
catch (IOException | InterruptedException | RuntimeException e)
{
if (e instanceof InterruptedException)
{
Thread.currentThread().interrupt();
}
return Optional.empty();
}
}
}
@@ -34,7 +34,6 @@ public class PlexPlayer
private String prefix;
private boolean staffChat;
private boolean vanished;
private boolean commandSpy;
// These fields are transient so MongoDB doesn't automatically drop them in.
@@ -42,8 +41,6 @@ public class PlexPlayer
private transient boolean muted;
private transient boolean lockedUp;
private long coins;
private List<String> ips = Lists.newArrayList();
private List<Punishment> punishments = Lists.newArrayList();
@@ -62,11 +59,8 @@ public class PlexPlayer
this.loginMessage = "";
this.prefix = "";
this.vanished = false;
this.commandSpy = false;
this.coins = 0;
if (loadPunishments)
{
this.checkMutesAndFreeze();
@@ -2,7 +2,8 @@ package dev.plex.punishment;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import dev.plex.player.PlayerService;
import dev.plex.api.punishment.PunishmentSource;
import dev.plex.player.PlayerNameResolver;
import dev.plex.util.PlexUtils;
import dev.plex.util.TimeUtils;
import dev.plex.util.adapter.ZonedDateTimeAdapter;
@@ -24,12 +25,9 @@ public class Punishment
@NotNull
private final UUID punished;
private final UUID punisher;
// Optional display attribution for punishers without a Minecraft UUID
// (e.g. web staff signed in via XenForo). When non-null, render this in
// place of the UUID-based name lookup.
private String punisherName;
private PunishmentSource source;
private String punisherReference;
private String ip;
private String punishedUsername;
private PunishmentType type;
private String reason;
private boolean customTime;
@@ -41,32 +39,33 @@ public class Punishment
{
this.punished = punished;
this.punisher = punisher;
this.source = punisher == null ? PunishmentSource.CONSOLE : PunishmentSource.PLAYER;
this.issueDate = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
}
public static Component generateBanMessage(Punishment punishment, String banUrl, PlayerService playerService)
public static Component generateBanMessage(Punishment punishment, String banUrl, PlayerNameResolver playerNameResolver)
{
return PlexUtils.messageComponent("banMessage", banUrl, punishment.getReason(), TimeUtils.useTimezone(punishment.getEndDate()), punisherDisplayName(punishment, playerService));
return PlexUtils.messageComponent("banMessage", banUrl, punishment.getReason(), TimeUtils.useTimezone(punishment.getEndDate()), punisherDisplayName(punishment, playerNameResolver));
}
public static Component generateKickMessage(Punishment punishment, PlayerService playerService)
public static Component generateKickMessage(Punishment punishment, PlayerNameResolver playerNameResolver)
{
return PlexUtils.messageComponent("kickMessage", punishment.getReason(), punisherDisplayName(punishment, playerService));
return PlexUtils.messageComponent("kickMessage", punishment.getReason(), punisherDisplayName(punishment, playerNameResolver));
}
/**
* Resolves the human-readable punisher attribution for display.
* Prefers the explicit {@link #punisherName} (used for off-server
* sources such as XenForo staff acting via the web HTTPD), falling
* back to a UUID lookup, and finally "CONSOLE" when the punisher is
* truly unknown.
*/
public static String punisherDisplayName(Punishment punishment, PlayerService playerService)
public static String punisherDisplayName(Punishment punishment, PlayerNameResolver playerNameResolver)
{
String explicit = punishment.getPunisherName();
if (explicit != null && !explicit.isEmpty()) return explicit;
if (punishment.getPunisher() == null) return "CONSOLE";
return playerService.getNameByUUID(punishment.getPunisher());
PunishmentSource source = punishment.getSource();
if (source == null)
{
source = punishment.getPunisher() == null ? PunishmentSource.CONSOLE : PunishmentSource.PLAYER;
}
return switch (source)
{
case PLAYER -> punishment.getPunisher() == null ? "CONSOLE" : playerNameResolver.resolve(punishment.getPunisher());
case CONSOLE -> "CONSOLE";
case WEB -> punishment.getPunisherReference() == null || punishment.getPunisherReference().isBlank() ? "WEB" : punishment.getPunisherReference();
};
}
public static Component generateIndefBanMessageWithReason(String type, String banUrl, String reason)
@@ -55,7 +55,6 @@ public class TimingService extends AbstractService
punishment.setReason(PlexUtils.messageString("nukerTempbanReason"));
if (player != null)
{
punishment.setPunishedUsername(player.getName());
punishment.setIp(player.getAddress().getAddress().getHostAddress());
}
punishment.setEndDate(TimeUtils.createDate("5m"));
@@ -2,6 +2,7 @@ package dev.plex.storage;
import com.zaxxer.hikari.HikariConfig;
import dev.plex.Plex;
import dev.plex.api.storage.SqlDialect;
import java.io.File;
import java.util.Arrays;
@@ -24,7 +25,7 @@ public enum StorageType
@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))";
return "CREATE TABLE IF NOT EXISTS " + quoteIdentifier(tableName) + " (scope VARCHAR(100) NOT NULL, version VARCHAR(100) NOT NULL, installed_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000), PRIMARY KEY (scope, version))";
}
},
@@ -42,7 +43,7 @@ public enum StorageType
@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)";
return "CREATE TABLE IF NOT EXISTS " + quoteIdentifier(tableName) + " (`scope` VARCHAR(100) NOT NULL, `version` VARCHAR(100) NOT NULL, `installed_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`scope`, `version`))";
}
},
@@ -81,9 +82,56 @@ public enum StorageType
public abstract void configure(HikariConfig config, Plex plugin);
public SqlDialect dialect()
{
return switch (this)
{
case SQLITE -> SqlDialect.SQLITE;
case MARIADB -> SqlDialect.MARIADB;
case POSTGRES -> SqlDialect.POSTGRES;
};
}
public String quoteIdentifier(String identifier)
{
return switch (this)
{
case MARIADB -> "`" + identifier + "`";
case SQLITE, POSTGRES -> "\"" + identifier + "\"";
};
}
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)";
return "CREATE TABLE IF NOT EXISTS " + quoteIdentifier(tableName) + " (scope VARCHAR(100) NOT NULL, version VARCHAR(100) NOT NULL, installed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (scope, version))";
}
public String playerModuleDataUpsertSql()
{
return switch (this)
{
case SQLITE -> """
INSERT INTO player_module_data (player_uuid, module, data_key, value_json, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(player_uuid, module, data_key) DO UPDATE SET
value_json = excluded.value_json,
updated_at = excluded.updated_at
""";
case MARIADB -> """
INSERT INTO `player_module_data` (`player_uuid`, `module`, `data_key`, `value_json`, `updated_at`)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
`value_json` = VALUES(`value_json`),
`updated_at` = VALUES(`updated_at`)
""";
case POSTGRES -> """
INSERT INTO player_module_data (player_uuid, module, data_key, value_json, updated_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(player_uuid, module, data_key) DO UPDATE SET
value_json = excluded.value_json,
updated_at = excluded.updated_at
""";
};
}
public String getDisplayName()
@@ -9,24 +9,16 @@ 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
{
private static final String MIGRATION_TABLE = "plex_schema_history";
protected final Plex plugin;
private final HikariDataSource dataSource;
private final ConnectionSource connectionSource;
private final StorageType storageType;
private final MigrationRunner migrationRunner;
public Database(Plex plugin)
{
@@ -48,7 +40,8 @@ public class Database
try
{
this.connectionSource = new DataSourceConnectionSource(dataSource, config.getJdbcUrl());
runMigrations();
this.migrationRunner = new MigrationRunner(storageType);
this.migrationRunner.runCore(dataSource, getClass().getClassLoader(), List.of("001_initial_schema"));
}
catch (Exception e)
{
@@ -57,105 +50,7 @@ public class Database
}
}
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
public java.sql.Connection getConnection() throws java.sql.SQLException
{
return dataSource.getConnection();
}
@@ -0,0 +1,206 @@
package dev.plex.storage.database;
import dev.plex.module.PlexModule;
import dev.plex.storage.StorageType;
import dev.plex.util.PlexLog;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MigrationRunner
{
private static final String MIGRATION_TABLE = "plex_schema_history";
private static final Pattern VERSION_PATTERN = Pattern.compile("^[0-9]{3}_[a-z0-9_]+$");
private static final Pattern TABLE_TOKEN_PATTERN = Pattern.compile("\\{\\{table:([a-z0-9_]+)}}");
private final StorageType storageType;
public MigrationRunner(StorageType storageType)
{
this.storageType = storageType;
}
public void runCore(DataSource dataSource, ClassLoader classLoader, List<String> versions) throws SQLException
{
run(dataSource, "core", versions, version -> readCore(classLoader, version), Function.identity());
}
public void runModule(DataSource dataSource, PlexModule module, String scope, String resourceRoot, List<String> versions, Function<String, String> tableResolver) throws SQLException
{
run(dataSource, scope, versions, version -> readModule(module, resourceRoot, version), tableResolver);
}
private void run(DataSource dataSource, String scope, List<String> versions, ResourceReader reader, Function<String, String> tableResolver) throws SQLException
{
try (Connection connection = dataSource.getConnection())
{
ensureMigrationTable(connection);
for (String version : versions)
{
validateVersion(version);
if (hasMigration(connection, scope, version))
{
continue;
}
String script = replaceTableTokens(reader.read(version), tableResolver);
for (String sql : splitStatements(script))
{
try (Statement statement = connection.createStatement())
{
statement.execute(sql);
}
}
insertMigration(connection, scope, version);
PlexLog.log("Applied database migration " + scope + ":" + version);
}
}
}
private void ensureMigrationTable(Connection connection) throws SQLException
{
try (Statement statement = connection.createStatement())
{
statement.execute(storageType.migrationHistoryTableSql(MIGRATION_TABLE));
}
}
private boolean hasMigration(Connection connection, String scope, String version) throws SQLException
{
try (PreparedStatement statement = connection.prepareStatement("SELECT version FROM " + MIGRATION_TABLE + " WHERE scope = ? AND version = ?"))
{
statement.setString(1, scope);
statement.setString(2, version);
try (ResultSet resultSet = statement.executeQuery())
{
return resultSet.next();
}
}
}
private void insertMigration(Connection connection, String scope, String version) throws SQLException
{
try (PreparedStatement statement = connection.prepareStatement("INSERT INTO " + MIGRATION_TABLE + " (scope, version) VALUES (?, ?)"))
{
statement.setString(1, scope);
statement.setString(2, version);
statement.executeUpdate();
}
}
private void validateVersion(String version) throws SQLException
{
if (!VERSION_PATTERN.matcher(version).matches())
{
throw new SQLException("Invalid migration version: " + version);
}
}
private String readCore(ClassLoader classLoader, String version) throws SQLException
{
String resource = "db/migration/" + storageType.dialect().migrationDirectory() + "/" + version + ".sql";
try (InputStream stream = classLoader.getResourceAsStream(resource))
{
if (stream == null)
{
throw new SQLException("Missing database migration resource: " + resource);
}
return new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
catch (IOException e)
{
throw new SQLException("Failed to read database migration resource: " + resource, e);
}
}
private String readModule(PlexModule module, String resourceRoot, String version) throws SQLException
{
String resource = resourceRoot + "/" + storageType.dialect().migrationDirectory() + "/" + version + ".sql";
try (InputStream stream = module.getResource(resource))
{
if (stream == null)
{
throw new SQLException("Missing module migration resource: " + resource);
}
return new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
catch (IOException e)
{
throw new SQLException("Failed to read module migration resource: " + resource, e);
}
}
private String replaceTableTokens(String script, Function<String, String> tableResolver) throws SQLException
{
Matcher matcher = TABLE_TOKEN_PATTERN.matcher(script);
StringBuilder replaced = new StringBuilder();
while (matcher.find())
{
matcher.appendReplacement(replaced, Matcher.quoteReplacement(tableResolver.apply(matcher.group(1))));
}
matcher.appendTail(replaced);
if (replaced.toString().contains("{{table:"))
{
throw new SQLException("Unsupported table token in migration");
}
return replaced.toString();
}
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);
}
}
@FunctionalInterface
private interface ResourceReader
{
String read(String version) throws SQLException;
}
}
@@ -19,8 +19,8 @@ public class NoteEntity
@DatabaseField(columnName = "uuid", canBeNull = false, index = true, width = 46)
private String uuid;
@DatabaseField(columnName = "written_by", width = 46)
private String writtenBy;
@DatabaseField(columnName = "written_by_uuid", width = 46)
private String writtenByUuid;
@DatabaseField(columnName = "note", width = 2000)
private String note;
@@ -13,8 +13,8 @@ public class PlayerEntity
@DatabaseField(id = true, columnName = "uuid", width = 46)
private String uuid;
@DatabaseField(columnName = "name", width = 18)
private String name;
@DatabaseField(columnName = "last_known_name", width = 18, index = true)
private String lastKnownName;
@DatabaseField(columnName = "login_msg", width = 2000)
private String loginMessage;
@@ -28,12 +28,6 @@ public class PlayerEntity
@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;
@@ -13,17 +13,17 @@ 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 = "punished_uuid", canBeNull = false, index = true, width = 46)
private String punishedUuid;
@DatabaseField(columnName = "punisher", width = 46)
private String punisher;
@DatabaseField(columnName = "punisher_uuid", width = 46)
private String punisherUuid;
@DatabaseField(columnName = "punisherName", width = 64)
private String punisherName;
@DatabaseField(columnName = "source", width = 20)
private String source;
@DatabaseField(columnName = "punishedUsername", width = 16)
private String punishedUsername;
@DatabaseField(columnName = "punisher_reference", width = 200)
private String punisherReference;
@DatabaseField(columnName = "ip", width = 2000, index = true)
private String ip;
@@ -0,0 +1,47 @@
package dev.plex.storage.module;
import dev.plex.module.PlexModule;
import java.util.Locale;
public final class ModuleNames
{
private static final int MAX_PREFIX_LENGTH = 40;
private ModuleNames()
{
}
public static String prefix(PlexModule module)
{
String name = module.getPlexModuleFile().getName().toLowerCase(Locale.ROOT);
if (name.startsWith("module-"))
{
name = name.substring("module-".length());
}
name = name.replaceAll("[^a-z0-9]+", "_").replaceAll("^_+|_+$", "");
if (name.isBlank())
{
throw new IllegalArgumentException("Module name does not produce a valid storage prefix");
}
if (name.length() > MAX_PREFIX_LENGTH)
{
name = name.substring(0, MAX_PREFIX_LENGTH).replaceAll("_+$", "");
}
return name;
}
public static String table(String prefix, String localName)
{
return prefix + "_" + validateLocalName(localName);
}
public static String validateLocalName(String localName)
{
if (localName == null || !localName.matches("[a-z][a-z0-9_]{0,47}"))
{
throw new IllegalArgumentException("Invalid module table name: " + localName);
}
return localName;
}
}
@@ -0,0 +1,41 @@
package dev.plex.storage.module;
import dev.plex.Plex;
import dev.plex.api.storage.ModuleMigrations;
import dev.plex.module.PlexModule;
import java.sql.SQLException;
import java.util.List;
public class ServerModuleMigrations implements ModuleMigrations
{
private final Plex plugin;
private final PlexModule module;
private final ServerModuleStorage storage;
public ServerModuleMigrations(Plex plugin, PlexModule module, ServerModuleStorage storage)
{
this.plugin = plugin;
this.module = module;
this.storage = storage;
}
@Override
public void run(List<String> versions) throws SQLException
{
run("db/migration", versions);
}
@Override
public void run(String resourceRoot, List<String> versions) throws SQLException
{
plugin.getSqlConnection().getMigrationRunner().runModule(
plugin.getSqlConnection().getDataSource(),
module,
storage.scope(),
resourceRoot,
versions,
storage::quotedTable
);
}
}
@@ -0,0 +1,42 @@
package dev.plex.storage.module;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.DatabaseTableConfig;
import dev.plex.api.storage.ModuleOrm;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ServerModuleOrm implements ModuleOrm
{
private final ConnectionSource connectionSource;
private final ServerModuleStorage storage;
private final Map<String, Dao<?, ?>> daos = new ConcurrentHashMap<>();
public ServerModuleOrm(ConnectionSource connectionSource, ServerModuleStorage storage)
{
this.connectionSource = connectionSource;
this.storage = storage;
}
@Override
@SuppressWarnings("unchecked")
public <T, ID> Dao<T, ID> dao(Class<T> entityClass, String localTableName) throws SQLException
{
String key = entityClass.getName() + ":" + localTableName;
Dao<?, ?> existing = daos.get(key);
if (existing != null)
{
return (Dao<T, ID>) existing;
}
DatabaseTableConfig<T> tableConfig = DatabaseTableConfig.fromClass(connectionSource.getDatabaseType(), entityClass);
tableConfig.setTableName(storage.table(localTableName));
Dao<T, ID> dao = DaoManager.createDao(connectionSource, tableConfig);
daos.put(key, dao);
return dao;
}
}
@@ -0,0 +1,69 @@
package dev.plex.storage.module;
import com.j256.ormlite.misc.TransactionManager;
import dev.plex.Plex;
import dev.plex.api.storage.ModuleMigrations;
import dev.plex.api.storage.ModuleOrm;
import dev.plex.api.storage.ModuleStorage;
import dev.plex.api.storage.SqlCallable;
import dev.plex.module.PlexModule;
import java.sql.SQLException;
public class ServerModuleStorage implements ModuleStorage
{
private final Plex plugin;
private final PlexModule module;
private final String prefix;
private final ModuleMigrations migrations;
private final ModuleOrm orm;
public ServerModuleStorage(Plex plugin, PlexModule module)
{
this.plugin = plugin;
this.module = module;
this.prefix = ModuleNames.prefix(module);
this.migrations = new ServerModuleMigrations(plugin, module, this);
this.orm = new ServerModuleOrm(plugin.getSqlConnection().getConnectionSource(), this);
}
@Override
public String prefix()
{
return prefix;
}
@Override
public String table(String localName)
{
return ModuleNames.table(prefix, localName);
}
String quotedTable(String localName)
{
return plugin.getStorageType().quoteIdentifier(table(localName));
}
String scope()
{
return "module:" + prefix;
}
@Override
public ModuleMigrations migrations()
{
return migrations;
}
@Override
public ModuleOrm orm()
{
return orm;
}
@Override
public <T> T transaction(SqlCallable<T> callable) throws SQLException
{
return TransactionManager.callInTransaction(plugin.getSqlConnection().getConnectionSource(), callable::call);
}
}
@@ -0,0 +1,15 @@
package dev.plex.storage.player;
import com.google.gson.JsonElement;
import java.util.Optional;
import java.util.UUID;
public interface PlayerModuleDataRepository
{
Optional<JsonElement> get(UUID playerUuid, String module, String key);
void set(UUID playerUuid, String module, String key, JsonElement value);
void remove(UUID playerUuid, String module, String key);
}
@@ -57,7 +57,7 @@ public class SQLPlayerData implements PlayerRepository
{
try
{
return players.queryBuilder().where().eq("name", username).queryForFirst() != null;
return players.queryBuilder().where().eq("last_known_name", username).queryForFirst() != null;
}
catch (SQLException e)
{
@@ -84,7 +84,7 @@ public class SQLPlayerData implements PlayerRepository
try
{
PlayerEntity entity = players.queryForId(uuid.toString());
return entity == null ? null : entity.getName();
return entity == null ? null : entity.getLastKnownName();
}
catch (SQLException e)
{
@@ -102,7 +102,7 @@ public class SQLPlayerData implements PlayerRepository
{
try
{
return toPlayer(players.queryBuilder().limit(1L).where().eq("name", username).queryForFirst(), loadExtraData);
return toPlayer(players.queryBuilder().limit(1L).where().eq("last_known_name", username).queryForFirst(), loadExtraData);
}
catch (SQLException e)
{
@@ -169,13 +169,11 @@ public class SQLPlayerData implements PlayerRepository
}
PlexPlayer plexPlayer = new PlexPlayer(UUID.fromString(entity.getUuid()), false);
plexPlayer.setName(entity.getName());
plexPlayer.setName(entity.getLastKnownName());
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());
if (loadExtraData)
{
@@ -189,13 +187,11 @@ public class SQLPlayerData implements PlayerRepository
{
PlayerEntity entity = new PlayerEntity();
entity.setUuid(player.getUuid().toString());
entity.setName(player.getName());
entity.setLastKnownName(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;
}
@@ -0,0 +1,86 @@
package dev.plex.storage.player;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import dev.plex.storage.SQLConnection;
import dev.plex.storage.StorageType;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import java.util.UUID;
public class SQLPlayerModuleData implements PlayerModuleDataRepository
{
private final SQLConnection sqlConnection;
private final StorageType storageType;
public SQLPlayerModuleData(SQLConnection sqlConnection, StorageType storageType)
{
this.sqlConnection = sqlConnection;
this.storageType = storageType;
}
@Override
public Optional<JsonElement> get(UUID playerUuid, String module, String key)
{
String sql = "SELECT value_json FROM player_module_data WHERE player_uuid = ? AND module = ? AND data_key = ?";
try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(sql))
{
statement.setString(1, playerUuid.toString());
statement.setString(2, module);
statement.setString(3, key);
try (ResultSet resultSet = statement.executeQuery())
{
if (!resultSet.next())
{
return Optional.empty();
}
return Optional.of(JsonParser.parseString(resultSet.getString("value_json")));
}
}
catch (SQLException | JsonSyntaxException e)
{
e.printStackTrace();
return Optional.empty();
}
}
@Override
public void set(UUID playerUuid, String module, String key, JsonElement value)
{
try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(storageType.playerModuleDataUpsertSql()))
{
statement.setString(1, playerUuid.toString());
statement.setString(2, module);
statement.setString(3, key);
statement.setString(4, value.toString());
statement.setLong(5, System.currentTimeMillis());
statement.executeUpdate();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
@Override
public void remove(UUID playerUuid, String module, String key)
{
String sql = "DELETE FROM player_module_data WHERE player_uuid = ? AND module = ? AND data_key = ?";
try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(sql))
{
statement.setString(1, playerUuid.toString());
statement.setString(2, module);
statement.setString(3, key);
statement.executeUpdate();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
}
@@ -16,6 +16,7 @@ import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -47,6 +48,7 @@ public class SQLNotes implements NoteRepository
return notes.queryForEq("uuid", uuid.toString()).stream()
.sorted(Comparator.comparingInt(NoteEntity::getId))
.map(this::toNote)
.flatMap(Optional::stream)
.toList();
}
catch (SQLException e)
@@ -96,16 +98,23 @@ public class SQLNotes implements NoteRepository
}, executor);
}
private Note toNote(NoteEntity entity)
private Optional<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;
try
{
Note note = new Note(
UUID.fromString(entity.getUuid()),
entity.getNote(),
UUID.fromString(entity.getWrittenByUuid()),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getTimestamp()), ZoneId.of(TimeUtils.TIMEZONE))
);
note.setId(entity.getId());
return Optional.of(note);
}
catch (IllegalArgumentException | NullPointerException e)
{
return Optional.empty();
}
}
private NoteEntity toEntity(Note note)
@@ -113,7 +122,7 @@ public class SQLNotes implements NoteRepository
NoteEntity entity = new NoteEntity();
entity.setId(note.getId());
entity.setUuid(note.getUuid().toString());
entity.setWrittenBy(note.getWrittenBy().toString());
entity.setWrittenByUuid(note.getWrittenBy().toString());
entity.setNote(note.getNote());
entity.setTimestamp(note.getTimestamp().toInstant().toEpochMilli());
return entity;
@@ -5,6 +5,7 @@ import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.stmt.UpdateBuilder;
import dev.plex.api.punishment.PunishmentSource;
import dev.plex.punishment.Punishment;
import dev.plex.punishment.PunishmentType;
import dev.plex.storage.database.entity.PunishmentEntity;
@@ -59,7 +60,7 @@ public class SQLPunishment implements PunishmentRepository
{
try
{
return punishments.queryForEq("punished", uuid.toString()).stream().map(this::toPunishment).toList();
return punishments.queryForEq("punished_uuid", uuid.toString()).stream().map(this::toPunishment).toList();
}
catch (SQLException e)
{
@@ -119,7 +120,7 @@ public class SQLPunishment implements PunishmentRepository
{
UpdateBuilder<PunishmentEntity, Long> update = punishments.updateBuilder();
update.updateColumnValue("active", active);
update.where().eq("punished", punished.toString()).and().eq("type", type.name());
update.where().eq("punished_uuid", punished.toString()).and().eq("type", type.name());
update.update();
}
catch (SQLException e)
@@ -130,13 +131,13 @@ public class SQLPunishment implements PunishmentRepository
private Punishment toPunishment(PunishmentEntity entity)
{
UUID punisher = entity.getPunisher() == null || entity.getPunisher().isBlank() ? null : UUID.fromString(entity.getPunisher());
Punishment punishment = new Punishment(UUID.fromString(entity.getPunished()), punisher);
UUID punisher = entity.getPunisherUuid() == null || entity.getPunisherUuid().isBlank() ? null : UUID.fromString(entity.getPunisherUuid());
Punishment punishment = new Punishment(UUID.fromString(entity.getPunishedUuid()), punisher);
punishment.setActive(entity.isActive());
punishment.setType(PunishmentType.valueOf(entity.getType()));
punishment.setCustomTime(entity.isCustomTime());
punishment.setPunishedUsername(entity.getPunishedUsername());
punishment.setPunisherName(entity.getPunisherName());
punishment.setSource(entity.getSource() == null ? punishment.getSource() : PunishmentSource.valueOf(entity.getSource()));
punishment.setPunisherReference(entity.getPunisherReference());
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());
@@ -147,10 +148,11 @@ public class SQLPunishment implements PunishmentRepository
private PunishmentEntity toEntity(Punishment punishment)
{
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.setPunishedUuid(punishment.getPunished().toString());
entity.setPunisherUuid(punishment.getPunisher() == null ? null : punishment.getPunisher().toString());
PunishmentSource source = punishment.getSource() == null ? (punishment.getPunisher() == null ? PunishmentSource.CONSOLE : PunishmentSource.PLAYER) : punishment.getSource();
entity.setSource(source.name());
entity.setPunisherReference(punishment.getPunisherReference());
entity.setIp(punishment.getIp());
entity.setType(punishment.getType().name());
entity.setReason(punishment.getReason());