mirror of
https://github.com/plexusorg/Plex.git
synced 2026-06-04 05:26:55 +00:00
New database API
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
plugins {
|
plugins {
|
||||||
java
|
`java-library`
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api("com.j256.ormlite:ormlite-core:6.1")
|
||||||
|
api("com.google.code.gson:gson:2.13.2")
|
||||||
|
|
||||||
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+")
|
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+")
|
||||||
compileOnly("org.apache.logging.log4j:log4j-api:2.26.0")
|
compileOnly("org.apache.logging.log4j:log4j-api:2.26.0")
|
||||||
compileOnly("com.google.code.gson:gson:2.13.2")
|
|
||||||
compileOnly("org.jetbrains:annotations:26.1.0")
|
compileOnly("org.jetbrains:annotations:26.1.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,4 +26,4 @@ publishing {
|
|||||||
from(components["java"])
|
from(components["java"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package dev.plex.api.player;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed key-value JSON storage for a player's module data.
|
||||||
|
*/
|
||||||
|
public interface PlayerModuleData
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets a raw JSON value.
|
||||||
|
*
|
||||||
|
* @param key data key
|
||||||
|
* @return stored JSON value, if present
|
||||||
|
*/
|
||||||
|
Optional<JsonElement> get(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and maps a JSON value to a Java type.
|
||||||
|
*
|
||||||
|
* @param key data key
|
||||||
|
* @param type target type
|
||||||
|
* @param <T> target type
|
||||||
|
* @return mapped value, if present
|
||||||
|
*/
|
||||||
|
<T> Optional<T> get(String key, Class<T> type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a string value.
|
||||||
|
*
|
||||||
|
* @param key data key
|
||||||
|
* @param fallback value returned when the key is absent or incompatible
|
||||||
|
* @return stored string or fallback
|
||||||
|
*/
|
||||||
|
String getString(String key, String fallback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a long value.
|
||||||
|
*
|
||||||
|
* @param key data key
|
||||||
|
* @param fallback value returned when the key is absent or incompatible
|
||||||
|
* @return stored long or fallback
|
||||||
|
*/
|
||||||
|
long getLong(String key, long fallback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a boolean value.
|
||||||
|
*
|
||||||
|
* @param key data key
|
||||||
|
* @param fallback value returned when the key is absent or incompatible
|
||||||
|
* @return stored boolean or fallback
|
||||||
|
*/
|
||||||
|
boolean getBoolean(String key, boolean fallback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a raw JSON value.
|
||||||
|
*
|
||||||
|
* @param key data key
|
||||||
|
* @param value JSON value to store
|
||||||
|
*/
|
||||||
|
void set(String key, JsonElement value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a Java value as JSON.
|
||||||
|
*
|
||||||
|
* @param key data key
|
||||||
|
* @param value value to serialize and store
|
||||||
|
*/
|
||||||
|
void set(String key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a stored value.
|
||||||
|
*
|
||||||
|
* @param key data key
|
||||||
|
*/
|
||||||
|
void remove(String key);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package dev.plex.api.player;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import dev.plex.module.PlexModule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks up Plex players through read-only API views.
|
* Looks up Plex players through read-only API views.
|
||||||
@@ -15,7 +16,7 @@ public interface PlayersApi
|
|||||||
* @param uuid player UUID
|
* @param uuid player UUID
|
||||||
* @return player view, if known
|
* @return player view, if known
|
||||||
*/
|
*/
|
||||||
Optional<? extends PlexPlayerView> byUuid(UUID uuid);
|
Optional<? extends PlexPlayerView> player(UUID uuid);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks up a player by name.
|
* Looks up a player by name.
|
||||||
@@ -31,4 +32,13 @@ public interface PlayersApi
|
|||||||
* @return names of currently online players
|
* @return names of currently online players
|
||||||
*/
|
*/
|
||||||
List<String> onlineNames();
|
List<String> onlineNames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns module-scoped data storage for a player.
|
||||||
|
*
|
||||||
|
* @param module module requesting player data
|
||||||
|
* @param playerUuid player UUID
|
||||||
|
* @return module-scoped player data storage
|
||||||
|
*/
|
||||||
|
PlayerModuleData moduleData(PlexModule module, UUID playerUuid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,18 @@ import java.util.UUID;
|
|||||||
*
|
*
|
||||||
* @param punished UUID of the player being punished
|
* @param punished UUID of the player being punished
|
||||||
* @param punisher UUID of the actor issuing the punishment
|
* @param punisher UUID of the actor issuing the punishment
|
||||||
* @param punisherName display name of the actor issuing the punishment
|
* @param source source that issued the punishment
|
||||||
|
* @param punisherReference source-specific actor reference
|
||||||
* @param ip IP address associated with the punished player
|
* @param ip IP address associated with the punished player
|
||||||
* @param punishedUsername username of the punished player
|
|
||||||
* @param type punishment type to apply
|
* @param type punishment type to apply
|
||||||
* @param reason punishment reason
|
* @param reason punishment reason
|
||||||
* @param customTime whether the punishment uses a custom duration
|
* @param customTime whether the punishment uses a custom duration
|
||||||
* @param active whether the punishment should start active
|
* @param active whether the punishment should start active
|
||||||
* @param endDate punishment end date, or {@code null} for punishments without an end date
|
* @param endDate punishment end date, or {@code null} for punishments without an end date
|
||||||
*/
|
*/
|
||||||
public record PunishmentRequest(UUID punished, UUID punisher, String punisherName, String ip,
|
public record PunishmentRequest(UUID punished, UUID punisher, PunishmentSource source,
|
||||||
String punishedUsername, PunishmentType type, String reason,
|
String punisherReference, String ip, PunishmentType type,
|
||||||
boolean customTime, boolean active, ZonedDateTime endDate)
|
String reason, boolean customTime, boolean active,
|
||||||
|
ZonedDateTime endDate)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package dev.plex.api.punishment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source that issued a punishment.
|
||||||
|
*/
|
||||||
|
public enum PunishmentSource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Punishment issued by an in-game player.
|
||||||
|
*/
|
||||||
|
PLAYER,
|
||||||
|
/**
|
||||||
|
* Punishment issued by the server console.
|
||||||
|
*/
|
||||||
|
CONSOLE,
|
||||||
|
/**
|
||||||
|
* Punishment issued by a web or external integration.
|
||||||
|
*/
|
||||||
|
WEB
|
||||||
|
}
|
||||||
@@ -23,11 +23,25 @@ public interface PunishmentView
|
|||||||
UUID punisher();
|
UUID punisher();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the display name of the actor who issued the punishment.
|
* Returns the source that issued the punishment.
|
||||||
*
|
*
|
||||||
* @return display name of the actor who issued the punishment
|
* @return punishment source
|
||||||
*/
|
*/
|
||||||
String punisherName();
|
PunishmentSource source();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the source-specific punisher reference.
|
||||||
|
*
|
||||||
|
* @return punisher reference, or {@code null} when not applicable
|
||||||
|
*/
|
||||||
|
String punisherReference();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the resolved display name for the punishment actor.
|
||||||
|
*
|
||||||
|
* @return display name for the punishment actor
|
||||||
|
*/
|
||||||
|
String punisherDisplayName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IP address associated with the punished player.
|
* Returns the IP address associated with the punished player.
|
||||||
@@ -36,13 +50,6 @@ public interface PunishmentView
|
|||||||
*/
|
*/
|
||||||
String ip();
|
String ip();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username of the punished player.
|
|
||||||
*
|
|
||||||
* @return username of the punished player
|
|
||||||
*/
|
|
||||||
String punishedUsername();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the punishment type.
|
* Returns the punishment type.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package dev.plex.api.storage;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs database migrations for a module storage namespace.
|
||||||
|
*/
|
||||||
|
public interface ModuleMigrations
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Runs migrations from the default module migration resource root.
|
||||||
|
*
|
||||||
|
* @param versions migration versions to apply
|
||||||
|
* @throws SQLException if a migration cannot be applied
|
||||||
|
*/
|
||||||
|
void run(List<String> versions) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs migrations from a custom module migration resource root.
|
||||||
|
*
|
||||||
|
* @param resourceRoot resource root containing dialect migration folders
|
||||||
|
* @param versions migration versions to apply
|
||||||
|
* @throws SQLException if a migration cannot be applied
|
||||||
|
*/
|
||||||
|
void run(String resourceRoot, List<String> versions) throws SQLException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package dev.plex.api.storage;
|
||||||
|
|
||||||
|
import com.j256.ormlite.dao.Dao;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates ORMLite DAOs for module-scoped tables.
|
||||||
|
*/
|
||||||
|
public interface ModuleOrm
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates or returns a cached DAO for a module-local table.
|
||||||
|
*
|
||||||
|
* @param entityClass ORMLite entity class
|
||||||
|
* @param localTableName module-local table name
|
||||||
|
* @param <T> entity type
|
||||||
|
* @param <ID> entity ID type
|
||||||
|
* @return ORMLite DAO using the module-prefixed physical table
|
||||||
|
* @throws SQLException if the DAO cannot be created
|
||||||
|
*/
|
||||||
|
<T, ID> Dao<T, ID> dao(Class<T> entityClass, String localTableName) throws SQLException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package dev.plex.api.storage;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module-scoped storage namespace.
|
||||||
|
*/
|
||||||
|
public interface ModuleStorage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the validated module table prefix.
|
||||||
|
*
|
||||||
|
* @return module table prefix
|
||||||
|
*/
|
||||||
|
String prefix();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a local table name to the module's physical table name.
|
||||||
|
*
|
||||||
|
* @param localName module-local table name
|
||||||
|
* @return physical table name
|
||||||
|
*/
|
||||||
|
String table(String localName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns module migration operations.
|
||||||
|
*
|
||||||
|
* @return module migration operations
|
||||||
|
*/
|
||||||
|
ModuleMigrations migrations();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns module ORMLite DAO operations.
|
||||||
|
*
|
||||||
|
* @return module ORMLite DAO operations
|
||||||
|
*/
|
||||||
|
ModuleOrm orm();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs work inside a storage transaction.
|
||||||
|
*
|
||||||
|
* @param callable work to run
|
||||||
|
* @param <T> callback result type
|
||||||
|
* @return callback result
|
||||||
|
* @throws SQLException if the transaction cannot complete
|
||||||
|
*/
|
||||||
|
<T> T transaction(SqlCallable<T> callable) throws SQLException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package dev.plex.api.storage;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used for SQL work that does not receive a connection directly.
|
||||||
|
*
|
||||||
|
* @param <T> callback result type
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface SqlCallable<T>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Runs SQL work.
|
||||||
|
*
|
||||||
|
* @return callback result
|
||||||
|
* @throws SQLException if the SQL work cannot complete
|
||||||
|
*/
|
||||||
|
T call() throws SQLException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.plex.api.storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL dialects supported by Plex storage.
|
||||||
|
*/
|
||||||
|
public enum SqlDialect
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* SQLite storage.
|
||||||
|
*/
|
||||||
|
SQLITE("sqlite"),
|
||||||
|
/**
|
||||||
|
* MariaDB or MySQL-compatible storage.
|
||||||
|
*/
|
||||||
|
MARIADB("mariadb"),
|
||||||
|
/**
|
||||||
|
* PostgreSQL storage.
|
||||||
|
*/
|
||||||
|
POSTGRES("postgres");
|
||||||
|
|
||||||
|
private final String migrationDirectory;
|
||||||
|
|
||||||
|
SqlDialect(String migrationDirectory)
|
||||||
|
{
|
||||||
|
this.migrationDirectory = migrationDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the resource directory name used for dialect-specific migrations.
|
||||||
|
*
|
||||||
|
* @return migration resource directory name
|
||||||
|
*/
|
||||||
|
public String migrationDirectory()
|
||||||
|
{
|
||||||
|
return migrationDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package dev.plex.api.storage;
|
|||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import dev.plex.module.PlexModule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides controlled access to Plex SQL storage.
|
* Provides controlled access to Plex SQL storage.
|
||||||
@@ -18,6 +19,21 @@ public interface StorageApi
|
|||||||
*/
|
*/
|
||||||
<T> T withConnection(SqlFunction<T> function) throws SQLException;
|
<T> T withConnection(SqlFunction<T> function) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns storage operations scoped to a module namespace.
|
||||||
|
*
|
||||||
|
* @param module module requesting storage
|
||||||
|
* @return module-scoped storage operations
|
||||||
|
*/
|
||||||
|
ModuleStorage forModule(PlexModule module);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured SQL dialect.
|
||||||
|
*
|
||||||
|
* @return configured SQL dialect
|
||||||
|
*/
|
||||||
|
SqlDialect dialect();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL callback used by {@link #withConnection(SqlFunction)}.
|
* SQL callback used by {@link #withConnection(SqlFunction)}.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -45,11 +45,22 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
private PlexApi api;
|
private PlexApi api;
|
||||||
private PlexModule module;
|
private PlexModule module;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a command using explicit command metadata.
|
||||||
|
*
|
||||||
|
* @param commandSpec command metadata
|
||||||
|
*/
|
||||||
protected SimplePlexCommand(CommandSpec commandSpec)
|
protected SimplePlexCommand(CommandSpec commandSpec)
|
||||||
{
|
{
|
||||||
this.commandSpec = commandSpec;
|
this.commandSpec = commandSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a command spec builder for the given command name.
|
||||||
|
*
|
||||||
|
* @param name primary command name
|
||||||
|
* @return command spec builder
|
||||||
|
*/
|
||||||
protected static CommandSpec.Builder command(String name)
|
protected static CommandSpec.Builder command(String name)
|
||||||
{
|
{
|
||||||
return CommandSpec.builder(name);
|
return CommandSpec.builder(name);
|
||||||
@@ -82,6 +93,14 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
return command.build();
|
return command.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the Brigadier command tree for this command.
|
||||||
|
*
|
||||||
|
* <p>The default tree accepts optional greedy string arguments and dispatches
|
||||||
|
* them to {@link #execute(CommandSender, Player, String[])}.</p>
|
||||||
|
*
|
||||||
|
* @param command root command literal builder
|
||||||
|
*/
|
||||||
protected void configureCommand(LiteralArgumentBuilder<CommandSourceStack> command)
|
protected void configureCommand(LiteralArgumentBuilder<CommandSourceStack> command)
|
||||||
{
|
{
|
||||||
command.executes(context -> dispatchCommand(context, new String[0]));
|
command.executes(context -> dispatchCommand(context, new String[0]));
|
||||||
@@ -90,13 +109,36 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
.executes(context -> dispatchCommand(context, splitExecutionArgs(StringArgumentType.getString(context, "args")))));
|
.executes(context -> dispatchCommand(context, splitExecutionArgs(StringArgumentType.getString(context, "args")))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes this command.
|
||||||
|
*
|
||||||
|
* @param sender command sender
|
||||||
|
* @param player player sender, or {@code null} when the sender is not a player
|
||||||
|
* @param args command arguments
|
||||||
|
* @return component to send to the sender, or {@code null} to send no response
|
||||||
|
*/
|
||||||
protected abstract Component execute(@NotNull CommandSender sender, @Nullable Player player, @NotNull String[] args);
|
protected abstract Component execute(@NotNull CommandSender sender, @Nullable Player player, @NotNull String[] args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns tab-completion suggestions for this command.
|
||||||
|
*
|
||||||
|
* @param sender command sender
|
||||||
|
* @param alias command alias used by the sender
|
||||||
|
* @param args current command arguments
|
||||||
|
* @return suggested completions
|
||||||
|
* @throws IllegalArgumentException when suggestions cannot be produced for the supplied arguments
|
||||||
|
*/
|
||||||
protected @NotNull List<String> suggestions(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
|
protected @NotNull List<String> suggestions(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
|
||||||
{
|
{
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the bound Plex API.
|
||||||
|
*
|
||||||
|
* @return bound Plex API
|
||||||
|
* @throws IllegalStateException when the command has not been bound to the API
|
||||||
|
*/
|
||||||
protected PlexApi api()
|
protected PlexApi api()
|
||||||
{
|
{
|
||||||
if (api == null)
|
if (api == null)
|
||||||
@@ -106,26 +148,56 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an ampersand-colorized legacy message to an audience.
|
||||||
|
*
|
||||||
|
* @param audience message recipient
|
||||||
|
* @param message legacy message text
|
||||||
|
*/
|
||||||
protected void send(Audience audience, String message)
|
protected void send(Audience audience, String message)
|
||||||
{
|
{
|
||||||
audience.sendMessage(componentFromString(message));
|
audience.sendMessage(componentFromString(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a component to an audience.
|
||||||
|
*
|
||||||
|
* @param audience message recipient
|
||||||
|
* @param component component to send
|
||||||
|
*/
|
||||||
protected void send(Audience audience, Component component)
|
protected void send(Audience audience, Component component)
|
||||||
{
|
{
|
||||||
audience.sendMessage(component);
|
audience.sendMessage(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts a MiniMessage-formatted message.
|
||||||
|
*
|
||||||
|
* @param miniMessage MiniMessage-formatted message
|
||||||
|
*/
|
||||||
protected void broadcast(String miniMessage)
|
protected void broadcast(String miniMessage)
|
||||||
{
|
{
|
||||||
api().messages().broadcast(miniMessage);
|
api().messages().broadcast(miniMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts a component.
|
||||||
|
*
|
||||||
|
* @param component component to broadcast
|
||||||
|
*/
|
||||||
protected void broadcast(Component component)
|
protected void broadcast(Component component)
|
||||||
{
|
{
|
||||||
api().messages().broadcast(component);
|
api().messages().broadcast(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a sender has a permission node.
|
||||||
|
*
|
||||||
|
* @param sender command sender
|
||||||
|
* @param permission permission node to check
|
||||||
|
* @return {@code true} when the sender can use the permission
|
||||||
|
* @throws CommandFailException when the sender lacks the permission
|
||||||
|
*/
|
||||||
protected boolean checkPermission(CommandSender sender, String permission)
|
protected boolean checkPermission(CommandSender sender, String permission)
|
||||||
{
|
{
|
||||||
if (permission.isEmpty() || isConsole(sender) || sender.hasPermission(permission))
|
if (permission.isEmpty() || isConsole(sender) || sender.hasPermission(permission))
|
||||||
@@ -135,31 +207,68 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
throw new CommandFailException(messageString("noPermissionNode", permission));
|
throw new CommandFailException(messageString("noPermissionNode", permission));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a sender has a permission node without throwing an exception.
|
||||||
|
*
|
||||||
|
* @param sender command sender
|
||||||
|
* @param permission permission node to check
|
||||||
|
* @return {@code true} when the sender can use the permission
|
||||||
|
*/
|
||||||
protected boolean silentCheckPermission(CommandSender sender, String permission)
|
protected boolean silentCheckPermission(CommandSender sender, String permission)
|
||||||
{
|
{
|
||||||
return permission.isEmpty() || isConsole(sender) || sender.hasPermission(permission);
|
return permission.isEmpty() || isConsole(sender) || sender.hasPermission(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the standard no-permission message for this command's permission node.
|
||||||
|
*
|
||||||
|
* @return no-permission component
|
||||||
|
*/
|
||||||
protected Component permissionMessage()
|
protected Component permissionMessage()
|
||||||
{
|
{
|
||||||
return permissionMessage(getPermission());
|
return permissionMessage(getPermission());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the standard no-permission message for a permission node.
|
||||||
|
*
|
||||||
|
* @param permission permission node
|
||||||
|
* @return no-permission component
|
||||||
|
*/
|
||||||
protected Component permissionMessage(String permission)
|
protected Component permissionMessage(String permission)
|
||||||
{
|
{
|
||||||
return messageComponent("noPermissionNode", permission);
|
return messageComponent("noPermissionNode", permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a sender's UUID when the sender is a player.
|
||||||
|
*
|
||||||
|
* @param sender command sender
|
||||||
|
* @return player UUID, or {@code null} for non-player senders
|
||||||
|
*/
|
||||||
protected @Nullable UUID getUUID(CommandSender sender)
|
protected @Nullable UUID getUUID(CommandSender sender)
|
||||||
{
|
{
|
||||||
return sender instanceof Player player ? player.getUniqueId() : null;
|
return sender instanceof Player player ? player.getUniqueId() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a sender is not a player.
|
||||||
|
*
|
||||||
|
* @param sender command sender
|
||||||
|
* @return {@code true} when the sender is not a player
|
||||||
|
*/
|
||||||
protected boolean isConsole(CommandSender sender)
|
protected boolean isConsole(CommandSender sender)
|
||||||
{
|
{
|
||||||
return !(sender instanceof Player);
|
return !(sender instanceof Player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a configured message as a component.
|
||||||
|
*
|
||||||
|
* @param key message key
|
||||||
|
* @param objects replacement values
|
||||||
|
* @return resolved message component
|
||||||
|
*/
|
||||||
protected Component messageComponent(String key, Object... objects)
|
protected Component messageComponent(String key, Object... objects)
|
||||||
{
|
{
|
||||||
if (module != null)
|
if (module != null)
|
||||||
@@ -169,6 +278,13 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
return api().messages().messageComponent(key, objects);
|
return api().messages().messageComponent(key, objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a configured message as a component using component replacements.
|
||||||
|
*
|
||||||
|
* @param key message key
|
||||||
|
* @param objects replacement components
|
||||||
|
* @return resolved message component
|
||||||
|
*/
|
||||||
protected Component messageComponent(String key, Component... objects)
|
protected Component messageComponent(String key, Component... objects)
|
||||||
{
|
{
|
||||||
if (module != null)
|
if (module != null)
|
||||||
@@ -178,6 +294,13 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
return api().messages().messageComponent(key, objects);
|
return api().messages().messageComponent(key, objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a configured message as plain text.
|
||||||
|
*
|
||||||
|
* @param key message key
|
||||||
|
* @param objects replacement values
|
||||||
|
* @return resolved message text
|
||||||
|
*/
|
||||||
protected String messageString(String key, Object... objects)
|
protected String messageString(String key, Object... objects)
|
||||||
{
|
{
|
||||||
if (module != null)
|
if (module != null)
|
||||||
@@ -187,16 +310,34 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
return api().messages().messageString(key, objects);
|
return api().messages().messageString(key, objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this command's formatted usage component.
|
||||||
|
*
|
||||||
|
* @return formatted usage component
|
||||||
|
*/
|
||||||
protected Component usage()
|
protected Component usage()
|
||||||
{
|
{
|
||||||
return usage(getUsage());
|
return usage(getUsage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats command usage text with the standard usage prefix.
|
||||||
|
*
|
||||||
|
* @param usage usage text
|
||||||
|
* @return formatted usage component
|
||||||
|
*/
|
||||||
protected Component usage(String usage)
|
protected Component usage(String usage)
|
||||||
{
|
{
|
||||||
return messageComponent("correctUsagePrefix").append(componentFromString(usage).color(NamedTextColor.GRAY));
|
return messageComponent("correctUsagePrefix").append(componentFromString(usage).color(NamedTextColor.GRAY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an online player by UUID string or name.
|
||||||
|
*
|
||||||
|
* @param name UUID string or player name
|
||||||
|
* @return matching online player
|
||||||
|
* @throws PlayerNotFoundException when no matching online player exists
|
||||||
|
*/
|
||||||
protected Player getNonNullPlayer(String name)
|
protected Player getNonNullPlayer(String name)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -220,21 +361,44 @@ public abstract class SimplePlexCommand implements PlexCommand
|
|||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the names of currently online players.
|
||||||
|
*
|
||||||
|
* @return online player names
|
||||||
|
*/
|
||||||
protected List<String> onlinePlayerNames()
|
protected List<String> onlinePlayerNames()
|
||||||
{
|
{
|
||||||
return api().players().onlineNames();
|
return api().players().onlineNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts ampersand-colorized legacy text to a gray-default component.
|
||||||
|
*
|
||||||
|
* @param value legacy text
|
||||||
|
* @return deserialized component
|
||||||
|
*/
|
||||||
protected Component componentFromString(String value)
|
protected Component componentFromString(String value)
|
||||||
{
|
{
|
||||||
return LegacyComponentSerializer.legacyAmpersand().deserialize(value).colorIfAbsent(NamedTextColor.GRAY);
|
return LegacyComponentSerializer.legacyAmpersand().deserialize(value).colorIfAbsent(NamedTextColor.GRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts ampersand-colorized legacy text to a component without adding a default color.
|
||||||
|
*
|
||||||
|
* @param value legacy text
|
||||||
|
* @return deserialized component
|
||||||
|
*/
|
||||||
protected Component noColorComponentFromString(String value)
|
protected Component noColorComponentFromString(String value)
|
||||||
{
|
{
|
||||||
return LegacyComponentSerializer.legacyAmpersand().deserialize(value);
|
return LegacyComponentSerializer.legacyAmpersand().deserialize(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts MiniMessage-formatted text to a component.
|
||||||
|
*
|
||||||
|
* @param value MiniMessage-formatted text
|
||||||
|
* @return deserialized component
|
||||||
|
*/
|
||||||
protected Component mmString(String value)
|
protected Component mmString(String value)
|
||||||
{
|
{
|
||||||
return api().messages().miniMessage(value);
|
return api().messages().miniMessage(value);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import dev.plex.hook.CoreProtectHook;
|
|||||||
import dev.plex.hook.PrismHook;
|
import dev.plex.hook.PrismHook;
|
||||||
import dev.plex.hook.RollbackManager;
|
import dev.plex.hook.RollbackManager;
|
||||||
import dev.plex.module.ModuleManager;
|
import dev.plex.module.ModuleManager;
|
||||||
|
import dev.plex.player.PlayerNameResolver;
|
||||||
import dev.plex.player.PlayerService;
|
import dev.plex.player.PlayerService;
|
||||||
import dev.plex.player.PlexPlayer;
|
import dev.plex.player.PlexPlayer;
|
||||||
import dev.plex.punishment.PunishmentManager;
|
import dev.plex.punishment.PunishmentManager;
|
||||||
@@ -21,6 +22,8 @@ import dev.plex.storage.RedisConnection;
|
|||||||
import dev.plex.storage.SQLConnection;
|
import dev.plex.storage.SQLConnection;
|
||||||
import dev.plex.storage.StorageType;
|
import dev.plex.storage.StorageType;
|
||||||
import dev.plex.storage.player.SQLPlayerData;
|
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.SQLNotes;
|
||||||
import dev.plex.storage.punishment.SQLPunishment;
|
import dev.plex.storage.punishment.SQLPunishment;
|
||||||
import dev.plex.storage.repository.NoteRepository;
|
import dev.plex.storage.repository.NoteRepository;
|
||||||
@@ -65,7 +68,9 @@ public class Plex extends JavaPlugin
|
|||||||
|
|
||||||
private PlayerCache playerCache;
|
private PlayerCache playerCache;
|
||||||
private PlayerRepository playerRepository;
|
private PlayerRepository playerRepository;
|
||||||
|
private PlayerModuleDataRepository playerModuleDataRepository;
|
||||||
private PlayerService playerService;
|
private PlayerService playerService;
|
||||||
|
private PlayerNameResolver playerNameResolver;
|
||||||
|
|
||||||
private PunishmentRepository punishmentRepository;
|
private PunishmentRepository punishmentRepository;
|
||||||
private NoteRepository noteRepository;
|
private NoteRepository noteRepository;
|
||||||
@@ -217,8 +222,10 @@ public class Plex extends JavaPlugin
|
|||||||
|
|
||||||
punishmentRepository = new SQLPunishment(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor());
|
punishmentRepository = new SQLPunishment(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor());
|
||||||
playerRepository = new SQLPlayerData(sqlConnection.getConnectionSource(), punishmentRepository);
|
playerRepository = new SQLPlayerData(sqlConnection.getConnectionSource(), punishmentRepository);
|
||||||
|
playerModuleDataRepository = new SQLPlayerModuleData(sqlConnection, storageType);
|
||||||
noteRepository = new SQLNotes(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor());
|
noteRepository = new SQLNotes(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor());
|
||||||
playerService = new PlayerService(playerCache, playerRepository);
|
playerService = new PlayerService(playerCache, playerRepository);
|
||||||
|
playerNameResolver = new PlayerNameResolver(playerService);
|
||||||
|
|
||||||
new ListenerHandler(this);
|
new ListenerHandler(this);
|
||||||
commandHandler = new CommandHandler(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;
|
package dev.plex.api.impl;
|
||||||
|
|
||||||
import dev.plex.Plex;
|
import dev.plex.Plex;
|
||||||
|
import dev.plex.api.player.PlayerModuleData;
|
||||||
import dev.plex.api.player.PlayersApi;
|
import dev.plex.api.player.PlayersApi;
|
||||||
import dev.plex.api.player.PlexPlayerView;
|
import dev.plex.api.player.PlexPlayerView;
|
||||||
|
import dev.plex.module.PlexModule;
|
||||||
import dev.plex.player.PlexPlayer;
|
import dev.plex.player.PlexPlayer;
|
||||||
|
import dev.plex.storage.module.ModuleNames;
|
||||||
import dev.plex.util.PlexUtils;
|
import dev.plex.util.PlexUtils;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -15,9 +18,10 @@ final class DefaultPlayersApi implements PlayersApi
|
|||||||
|
|
||||||
DefaultPlayersApi(Plex plugin) { this.plugin = plugin; }
|
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> 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(DefaultPlexPlayerView::new); }
|
@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 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)
|
static PlexPlayer unwrap(PlexPlayerView view)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ package dev.plex.api.impl;
|
|||||||
|
|
||||||
import dev.plex.api.player.PlexPlayerView;
|
import dev.plex.api.player.PlexPlayerView;
|
||||||
import dev.plex.api.punishment.PunishmentView;
|
import dev.plex.api.punishment.PunishmentView;
|
||||||
|
import dev.plex.player.PlayerNameResolver;
|
||||||
import dev.plex.player.PlexPlayer;
|
import dev.plex.player.PlexPlayer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.bukkit.entity.Player;
|
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 UUID uuid() { return player.getUuid(); }
|
||||||
@Override public String name() { return player.getName(); }
|
@Override public String name() { return player.getName(); }
|
||||||
@Override public List<String> ips() { return List.copyOf(player.getIps()); }
|
@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 frozen() { return player.isFrozen(); }
|
||||||
@Override public boolean muted() { return player.isMuted(); }
|
@Override public boolean muted() { return player.isMuted(); }
|
||||||
@Override public boolean lockedUp() { return player.isLockedUp(); }
|
@Override public boolean lockedUp() { return player.isLockedUp(); }
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
package dev.plex.api.impl;
|
package dev.plex.api.impl;
|
||||||
|
|
||||||
|
import dev.plex.api.punishment.PunishmentSource;
|
||||||
import dev.plex.api.punishment.PunishmentType;
|
import dev.plex.api.punishment.PunishmentType;
|
||||||
import dev.plex.api.punishment.PunishmentView;
|
import dev.plex.api.punishment.PunishmentView;
|
||||||
|
import dev.plex.player.PlayerNameResolver;
|
||||||
import dev.plex.punishment.Punishment;
|
import dev.plex.punishment.Punishment;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.UUID;
|
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 punished() { return punishment.getPunished(); }
|
||||||
@Override public UUID punisher() { return punishment.getPunisher(); }
|
@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 ip() { return punishment.getIp(); }
|
||||||
@Override public String punishedUsername() { return punishment.getPunishedUsername(); }
|
|
||||||
@Override public PunishmentType type() { return PunishmentType.valueOf(punishment.getType().name()); }
|
@Override public PunishmentType type() { return PunishmentType.valueOf(punishment.getType().name()); }
|
||||||
@Override public String reason() { return punishment.getReason(); }
|
@Override public String reason() { return punishment.getReason(); }
|
||||||
@Override public boolean customTime() { return punishment.isCustomTime(); }
|
@Override public boolean customTime() { return punishment.isCustomTime(); }
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ final class DefaultPunishmentsApi implements PunishmentsApi
|
|||||||
PlexPlayer player = DefaultPlayersApi.unwrap(playerView);
|
PlexPlayer player = DefaultPlayersApi.unwrap(playerView);
|
||||||
if (player == null) player = plugin.getPlayerService().getPlayer(playerView.uuid());
|
if (player == null) player = plugin.getPlayerService().getPlayer(playerView.uuid());
|
||||||
Punishment punishment = new Punishment(request.punished(), request.punisher());
|
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.setIp(request.ip());
|
||||||
punishment.setPunishedUsername(request.punishedUsername());
|
|
||||||
punishment.setType(PunishmentType.valueOf(request.type().name()));
|
punishment.setType(PunishmentType.valueOf(request.type().name()));
|
||||||
punishment.setReason(request.reason());
|
punishment.setReason(request.reason());
|
||||||
punishment.setCustomTime(request.customTime());
|
punishment.setCustomTime(request.customTime());
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package dev.plex.api.impl;
|
package dev.plex.api.impl;
|
||||||
|
|
||||||
import dev.plex.Plex;
|
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.api.storage.StorageApi;
|
||||||
|
import dev.plex.module.PlexModule;
|
||||||
|
import dev.plex.storage.module.ServerModuleStorage;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
@@ -19,4 +23,16 @@ final class DefaultStorageApi implements StorageApi
|
|||||||
return function.apply(connection);
|
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.setReason(context.messageString("noReasonProvided"));
|
||||||
}
|
}
|
||||||
punishment.setPunishedUsername(plexPlayer.getName());
|
|
||||||
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
|
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
|
||||||
punishment.setEndDate(date.plusDays(1));
|
punishment.setEndDate(date.plusDays(1));
|
||||||
punishment.setCustomTime(false);
|
punishment.setCustomTime(false);
|
||||||
@@ -107,7 +106,7 @@ public class BanCMD extends ServerCommand
|
|||||||
PlexUtils.broadcast(context.messageComponent("banningPlayer", sender.getName(), plexPlayer.getName()));
|
PlexUtils.broadcast(context.messageComponent("banningPlayer", sender.getName(), plexPlayer.getName()));
|
||||||
if (player != null)
|
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());
|
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 com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
import dev.plex.command.ServerCommand;
|
import dev.plex.command.ServerCommand;
|
||||||
import dev.plex.command.ServerCommandContext;
|
import dev.plex.command.ServerCommandContext;
|
||||||
import dev.plex.punishment.Punishment;
|
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@@ -44,7 +41,7 @@ public class BanListCommand extends ServerCommand
|
|||||||
{
|
{
|
||||||
plugin.getPunishmentManager().getActiveBans().whenComplete((punishments, throwable) ->
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ public class FreezeCMD extends ServerCommand
|
|||||||
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
|
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
|
||||||
punishment.setEndDate(date.plusSeconds(plugin.config.getInt("punishments.freeze-timer", 300)));
|
punishment.setEndDate(date.plusSeconds(plugin.config.getInt("punishments.freeze-timer", 300)));
|
||||||
punishment.setType(PunishmentType.FREEZE);
|
punishment.setType(PunishmentType.FREEZE);
|
||||||
punishment.setPunishedUsername(player.getName());
|
|
||||||
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
||||||
punishment.setReason("");
|
punishment.setReason("");
|
||||||
punishment.setActive(true);
|
punishment.setActive(true);
|
||||||
|
|||||||
@@ -76,14 +76,13 @@ public class KickCMD extends ServerCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
punishment.setReason(reason);
|
punishment.setReason(reason);
|
||||||
punishment.setPunishedUsername(plexPlayer.getName());
|
|
||||||
punishment.setEndDate(ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE)));
|
punishment.setEndDate(ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE)));
|
||||||
punishment.setCustomTime(false);
|
punishment.setCustomTime(false);
|
||||||
punishment.setActive(false);
|
punishment.setActive(false);
|
||||||
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
||||||
plugin.getPunishmentManager().punish(plexPlayer, punishment);
|
plugin.getPunishmentManager().punish(plexPlayer, punishment);
|
||||||
PlexUtils.broadcast(context.messageComponent("kickedPlayer", sender.getName(), plexPlayer.getName()));
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ public class MuteCMD extends ServerCommand
|
|||||||
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
|
ZonedDateTime date = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
|
||||||
punishment.setEndDate(date.plusSeconds(plugin.config.getInt("punishments.mute-timer", 300)));
|
punishment.setEndDate(date.plusSeconds(plugin.config.getInt("punishments.mute-timer", 300)));
|
||||||
punishment.setType(PunishmentType.MUTE);
|
punishment.setType(PunishmentType.MUTE);
|
||||||
punishment.setPunishedUsername(player.getName());
|
|
||||||
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
||||||
punishment.setReason("");
|
punishment.setReason("");
|
||||||
punishment.setActive(true);
|
punishment.setActive(true);
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ public class NotesCMD extends ServerCommand
|
|||||||
AtomicReference<Component> noteList = new AtomicReference<>(context.messageComponent("notesHeader", plexPlayer.getName()));
|
AtomicReference<Component> noteList = new AtomicReference<>(context.messageComponent("notesHeader", plexPlayer.getName()));
|
||||||
for (Note note : notes)
|
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()));
|
noteLine = noteLine.append(context.messageComponent("noteLine", note.getNote()));
|
||||||
noteList.set(noteList.get().append(Component.newline()));
|
noteList.set(noteList.get().append(Component.newline()));
|
||||||
noteList.set(noteList.get().append(noteLine));
|
noteList.set(noteList.get().append(noteLine));
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ public class SmiteCMD extends ServerCommand
|
|||||||
punishment.setCustomTime(false);
|
punishment.setCustomTime(false);
|
||||||
punishment.setEndDate(ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE)));
|
punishment.setEndDate(ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE)));
|
||||||
punishment.setType(PunishmentType.SMITE);
|
punishment.setType(PunishmentType.SMITE);
|
||||||
punishment.setPunishedUsername(player.getName());
|
|
||||||
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
||||||
|
|
||||||
if (reason != null)
|
if (reason != null)
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ public class TempbanCMD extends ServerCommand
|
|||||||
{
|
{
|
||||||
punishment.setReason(context.messageString("noReasonProvided"));
|
punishment.setReason(context.messageString("noReasonProvided"));
|
||||||
}
|
}
|
||||||
punishment.setPunishedUsername(target.getName());
|
|
||||||
punishment.setEndDate(TimeUtils.createDate(args[1]));
|
punishment.setEndDate(TimeUtils.createDate(args[1]));
|
||||||
punishment.setCustomTime(false);
|
punishment.setCustomTime(false);
|
||||||
punishment.setActive(true);
|
punishment.setActive(true);
|
||||||
@@ -92,7 +91,7 @@ public class TempbanCMD extends ServerCommand
|
|||||||
PlexUtils.broadcast(context.messageComponent("banningPlayer", sender.getName(), target.getName()));
|
PlexUtils.broadcast(context.messageComponent("banningPlayer", sender.getName(), target.getName()));
|
||||||
if (player != null)
|
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)
|
if (rollBack)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ public class TempmuteCMD extends ServerCommand
|
|||||||
punishment.setCustomTime(true);
|
punishment.setCustomTime(true);
|
||||||
punishment.setEndDate(endDate);
|
punishment.setEndDate(endDate);
|
||||||
punishment.setType(PunishmentType.MUTE);
|
punishment.setType(PunishmentType.MUTE);
|
||||||
punishment.setPunishedUsername(player.getName());
|
|
||||||
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
punishment.setIp(player.getAddress().getAddress().getHostAddress().trim());
|
||||||
punishment.setReason(reason);
|
punishment.setReason(reason);
|
||||||
punishment.setActive(true);
|
punishment.setActive(true);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class BanListener extends ServerListenerBase
|
|||||||
PlexPlayer player = plugin.getPlayerService().getPlayer(event.getUniqueId());
|
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 ->
|
player.getPunishments().stream().filter(punishment -> (punishment.getType() == PunishmentType.BAN || punishment.getType() == PunishmentType.TEMPBAN) && punishment.isActive()).findFirst().ifPresent(punishment ->
|
||||||
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
Punishment ipBannedPunishment = plugin.getPunishmentManager().getBanByIP(event.getAddress().getHostAddress());
|
Punishment ipBannedPunishment = plugin.getPunishmentManager().getBanByIP(event.getAddress().getHostAddress());
|
||||||
@@ -66,7 +66,7 @@ public class BanListener extends ServerListenerBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED,
|
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()))
|
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());
|
plexPlayer.setName(player.getName());
|
||||||
plugin.getPlayerService().update(plexPlayer);
|
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 String prefix;
|
||||||
|
|
||||||
private boolean staffChat;
|
private boolean staffChat;
|
||||||
private boolean vanished;
|
|
||||||
private boolean commandSpy;
|
private boolean commandSpy;
|
||||||
|
|
||||||
// These fields are transient so MongoDB doesn't automatically drop them in.
|
// 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 muted;
|
||||||
private transient boolean lockedUp;
|
private transient boolean lockedUp;
|
||||||
|
|
||||||
private long coins;
|
|
||||||
|
|
||||||
private List<String> ips = Lists.newArrayList();
|
private List<String> ips = Lists.newArrayList();
|
||||||
|
|
||||||
private List<Punishment> punishments = Lists.newArrayList();
|
private List<Punishment> punishments = Lists.newArrayList();
|
||||||
@@ -62,11 +59,8 @@ public class PlexPlayer
|
|||||||
this.loginMessage = "";
|
this.loginMessage = "";
|
||||||
this.prefix = "";
|
this.prefix = "";
|
||||||
|
|
||||||
this.vanished = false;
|
|
||||||
this.commandSpy = false;
|
this.commandSpy = false;
|
||||||
|
|
||||||
this.coins = 0;
|
|
||||||
|
|
||||||
if (loadPunishments)
|
if (loadPunishments)
|
||||||
{
|
{
|
||||||
this.checkMutesAndFreeze();
|
this.checkMutesAndFreeze();
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package dev.plex.punishment;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
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.PlexUtils;
|
||||||
import dev.plex.util.TimeUtils;
|
import dev.plex.util.TimeUtils;
|
||||||
import dev.plex.util.adapter.ZonedDateTimeAdapter;
|
import dev.plex.util.adapter.ZonedDateTimeAdapter;
|
||||||
@@ -24,12 +25,9 @@ public class Punishment
|
|||||||
@NotNull
|
@NotNull
|
||||||
private final UUID punished;
|
private final UUID punished;
|
||||||
private final UUID punisher;
|
private final UUID punisher;
|
||||||
// Optional display attribution for punishers without a Minecraft UUID
|
private PunishmentSource source;
|
||||||
// (e.g. web staff signed in via XenForo). When non-null, render this in
|
private String punisherReference;
|
||||||
// place of the UUID-based name lookup.
|
|
||||||
private String punisherName;
|
|
||||||
private String ip;
|
private String ip;
|
||||||
private String punishedUsername;
|
|
||||||
private PunishmentType type;
|
private PunishmentType type;
|
||||||
private String reason;
|
private String reason;
|
||||||
private boolean customTime;
|
private boolean customTime;
|
||||||
@@ -41,32 +39,33 @@ public class Punishment
|
|||||||
{
|
{
|
||||||
this.punished = punished;
|
this.punished = punished;
|
||||||
this.punisher = punisher;
|
this.punisher = punisher;
|
||||||
|
this.source = punisher == null ? PunishmentSource.CONSOLE : PunishmentSource.PLAYER;
|
||||||
this.issueDate = ZonedDateTime.now(ZoneId.of(TimeUtils.TIMEZONE));
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static String punisherDisplayName(Punishment punishment, PlayerNameResolver 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)
|
|
||||||
{
|
{
|
||||||
String explicit = punishment.getPunisherName();
|
PunishmentSource source = punishment.getSource();
|
||||||
if (explicit != null && !explicit.isEmpty()) return explicit;
|
if (source == null)
|
||||||
if (punishment.getPunisher() == null) return "CONSOLE";
|
{
|
||||||
return playerService.getNameByUUID(punishment.getPunisher());
|
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)
|
public static Component generateIndefBanMessageWithReason(String type, String banUrl, String reason)
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ public class TimingService extends AbstractService
|
|||||||
punishment.setReason(PlexUtils.messageString("nukerTempbanReason"));
|
punishment.setReason(PlexUtils.messageString("nukerTempbanReason"));
|
||||||
if (player != null)
|
if (player != null)
|
||||||
{
|
{
|
||||||
punishment.setPunishedUsername(player.getName());
|
|
||||||
punishment.setIp(player.getAddress().getAddress().getHostAddress());
|
punishment.setIp(player.getAddress().getAddress().getHostAddress());
|
||||||
}
|
}
|
||||||
punishment.setEndDate(TimeUtils.createDate("5m"));
|
punishment.setEndDate(TimeUtils.createDate("5m"));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dev.plex.storage;
|
|||||||
|
|
||||||
import com.zaxxer.hikari.HikariConfig;
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
import dev.plex.Plex;
|
import dev.plex.Plex;
|
||||||
|
import dev.plex.api.storage.SqlDialect;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -24,7 +25,7 @@ public enum StorageType
|
|||||||
@Override
|
@Override
|
||||||
public String migrationHistoryTableSql(String tableName)
|
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
|
@Override
|
||||||
public String migrationHistoryTableSql(String tableName)
|
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 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)
|
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()
|
public String getDisplayName()
|
||||||
|
|||||||
@@ -9,24 +9,16 @@ import dev.plex.storage.StorageType;
|
|||||||
import dev.plex.util.PlexLog;
|
import dev.plex.util.PlexLog;
|
||||||
import lombok.Getter;
|
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;
|
import java.util.List;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class Database
|
public class Database
|
||||||
{
|
{
|
||||||
private static final String MIGRATION_TABLE = "plex_schema_history";
|
|
||||||
|
|
||||||
protected final Plex plugin;
|
protected final Plex plugin;
|
||||||
private final HikariDataSource dataSource;
|
private final HikariDataSource dataSource;
|
||||||
private final ConnectionSource connectionSource;
|
private final ConnectionSource connectionSource;
|
||||||
private final StorageType storageType;
|
private final StorageType storageType;
|
||||||
|
private final MigrationRunner migrationRunner;
|
||||||
|
|
||||||
public Database(Plex plugin)
|
public Database(Plex plugin)
|
||||||
{
|
{
|
||||||
@@ -48,7 +40,8 @@ public class Database
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.connectionSource = new DataSourceConnectionSource(dataSource, config.getJdbcUrl());
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -57,105 +50,7 @@ public class Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runMigrations() throws Exception
|
public java.sql.Connection getConnection() throws java.sql.SQLException
|
||||||
{
|
|
||||||
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();
|
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)
|
@DatabaseField(columnName = "uuid", canBeNull = false, index = true, width = 46)
|
||||||
private String uuid;
|
private String uuid;
|
||||||
|
|
||||||
@DatabaseField(columnName = "written_by", width = 46)
|
@DatabaseField(columnName = "written_by_uuid", width = 46)
|
||||||
private String writtenBy;
|
private String writtenByUuid;
|
||||||
|
|
||||||
@DatabaseField(columnName = "note", width = 2000)
|
@DatabaseField(columnName = "note", width = 2000)
|
||||||
private String note;
|
private String note;
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ public class PlayerEntity
|
|||||||
@DatabaseField(id = true, columnName = "uuid", width = 46)
|
@DatabaseField(id = true, columnName = "uuid", width = 46)
|
||||||
private String uuid;
|
private String uuid;
|
||||||
|
|
||||||
@DatabaseField(columnName = "name", width = 18)
|
@DatabaseField(columnName = "last_known_name", width = 18, index = true)
|
||||||
private String name;
|
private String lastKnownName;
|
||||||
|
|
||||||
@DatabaseField(columnName = "login_msg", width = 2000)
|
@DatabaseField(columnName = "login_msg", width = 2000)
|
||||||
private String loginMessage;
|
private String loginMessage;
|
||||||
@@ -28,12 +28,6 @@ public class PlayerEntity
|
|||||||
@DatabaseField(columnName = "ips", width = 2000)
|
@DatabaseField(columnName = "ips", width = 2000)
|
||||||
private String ips;
|
private String ips;
|
||||||
|
|
||||||
@DatabaseField(columnName = "coins")
|
|
||||||
private long coins;
|
|
||||||
|
|
||||||
@DatabaseField(columnName = "vanished")
|
|
||||||
private boolean vanished;
|
|
||||||
|
|
||||||
@DatabaseField(columnName = "commandspy")
|
@DatabaseField(columnName = "commandspy")
|
||||||
private boolean commandSpy;
|
private boolean commandSpy;
|
||||||
|
|
||||||
|
|||||||
@@ -13,17 +13,17 @@ public class PunishmentEntity
|
|||||||
@DatabaseField(generatedId = true, columnName = "id")
|
@DatabaseField(generatedId = true, columnName = "id")
|
||||||
private long id;
|
private long id;
|
||||||
|
|
||||||
@DatabaseField(columnName = "punished", canBeNull = false, index = true, width = 46)
|
@DatabaseField(columnName = "punished_uuid", canBeNull = false, index = true, width = 46)
|
||||||
private String punished;
|
private String punishedUuid;
|
||||||
|
|
||||||
@DatabaseField(columnName = "punisher", width = 46)
|
@DatabaseField(columnName = "punisher_uuid", width = 46)
|
||||||
private String punisher;
|
private String punisherUuid;
|
||||||
|
|
||||||
@DatabaseField(columnName = "punisherName", width = 64)
|
@DatabaseField(columnName = "source", width = 20)
|
||||||
private String punisherName;
|
private String source;
|
||||||
|
|
||||||
@DatabaseField(columnName = "punishedUsername", width = 16)
|
@DatabaseField(columnName = "punisher_reference", width = 200)
|
||||||
private String punishedUsername;
|
private String punisherReference;
|
||||||
|
|
||||||
@DatabaseField(columnName = "ip", width = 2000, index = true)
|
@DatabaseField(columnName = "ip", width = 2000, index = true)
|
||||||
private String ip;
|
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
|
try
|
||||||
{
|
{
|
||||||
return players.queryBuilder().where().eq("name", username).queryForFirst() != null;
|
return players.queryBuilder().where().eq("last_known_name", username).queryForFirst() != null;
|
||||||
}
|
}
|
||||||
catch (SQLException e)
|
catch (SQLException e)
|
||||||
{
|
{
|
||||||
@@ -84,7 +84,7 @@ public class SQLPlayerData implements PlayerRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
PlayerEntity entity = players.queryForId(uuid.toString());
|
PlayerEntity entity = players.queryForId(uuid.toString());
|
||||||
return entity == null ? null : entity.getName();
|
return entity == null ? null : entity.getLastKnownName();
|
||||||
}
|
}
|
||||||
catch (SQLException e)
|
catch (SQLException e)
|
||||||
{
|
{
|
||||||
@@ -102,7 +102,7 @@ public class SQLPlayerData implements PlayerRepository
|
|||||||
{
|
{
|
||||||
try
|
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)
|
catch (SQLException e)
|
||||||
{
|
{
|
||||||
@@ -169,13 +169,11 @@ public class SQLPlayerData implements PlayerRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlexPlayer plexPlayer = new PlexPlayer(UUID.fromString(entity.getUuid()), false);
|
PlexPlayer plexPlayer = new PlexPlayer(UUID.fromString(entity.getUuid()), false);
|
||||||
plexPlayer.setName(entity.getName());
|
plexPlayer.setName(entity.getLastKnownName());
|
||||||
plexPlayer.setLoginMessage(entity.getLoginMessage());
|
plexPlayer.setLoginMessage(entity.getLoginMessage());
|
||||||
plexPlayer.setPrefix(entity.getPrefix());
|
plexPlayer.setPrefix(entity.getPrefix());
|
||||||
plexPlayer.setStaffChat(entity.isStaffChat());
|
plexPlayer.setStaffChat(entity.isStaffChat());
|
||||||
plexPlayer.setIps(parseIps(entity.getIps()));
|
plexPlayer.setIps(parseIps(entity.getIps()));
|
||||||
plexPlayer.setCoins(entity.getCoins());
|
|
||||||
plexPlayer.setVanished(entity.isVanished());
|
|
||||||
plexPlayer.setCommandSpy(entity.isCommandSpy());
|
plexPlayer.setCommandSpy(entity.isCommandSpy());
|
||||||
if (loadExtraData)
|
if (loadExtraData)
|
||||||
{
|
{
|
||||||
@@ -189,13 +187,11 @@ public class SQLPlayerData implements PlayerRepository
|
|||||||
{
|
{
|
||||||
PlayerEntity entity = new PlayerEntity();
|
PlayerEntity entity = new PlayerEntity();
|
||||||
entity.setUuid(player.getUuid().toString());
|
entity.setUuid(player.getUuid().toString());
|
||||||
entity.setName(player.getName());
|
entity.setLastKnownName(player.getName());
|
||||||
entity.setLoginMessage(player.getLoginMessage());
|
entity.setLoginMessage(player.getLoginMessage());
|
||||||
entity.setPrefix(player.getPrefix());
|
entity.setPrefix(player.getPrefix());
|
||||||
entity.setStaffChat(player.isStaffChat());
|
entity.setStaffChat(player.isStaffChat());
|
||||||
entity.setIps(GSON.toJson(player.getIps()));
|
entity.setIps(GSON.toJson(player.getIps()));
|
||||||
entity.setCoins(player.getCoins());
|
|
||||||
entity.setVanished(player.isVanished());
|
|
||||||
entity.setCommandSpy(player.isCommandSpy());
|
entity.setCommandSpy(player.isCommandSpy());
|
||||||
return entity;
|
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.time.ZonedDateTime;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -47,6 +48,7 @@ public class SQLNotes implements NoteRepository
|
|||||||
return notes.queryForEq("uuid", uuid.toString()).stream()
|
return notes.queryForEq("uuid", uuid.toString()).stream()
|
||||||
.sorted(Comparator.comparingInt(NoteEntity::getId))
|
.sorted(Comparator.comparingInt(NoteEntity::getId))
|
||||||
.map(this::toNote)
|
.map(this::toNote)
|
||||||
|
.flatMap(Optional::stream)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
catch (SQLException e)
|
catch (SQLException e)
|
||||||
@@ -96,16 +98,23 @@ public class SQLNotes implements NoteRepository
|
|||||||
}, executor);
|
}, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Note toNote(NoteEntity entity)
|
private Optional<Note> toNote(NoteEntity entity)
|
||||||
{
|
{
|
||||||
Note note = new Note(
|
try
|
||||||
UUID.fromString(entity.getUuid()),
|
{
|
||||||
entity.getNote(),
|
Note note = new Note(
|
||||||
UUID.fromString(entity.getWrittenBy()),
|
UUID.fromString(entity.getUuid()),
|
||||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getTimestamp()), ZoneId.of(TimeUtils.TIMEZONE))
|
entity.getNote(),
|
||||||
);
|
UUID.fromString(entity.getWrittenByUuid()),
|
||||||
note.setId(entity.getId());
|
ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getTimestamp()), ZoneId.of(TimeUtils.TIMEZONE))
|
||||||
return note;
|
);
|
||||||
|
note.setId(entity.getId());
|
||||||
|
return Optional.of(note);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e)
|
||||||
|
{
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NoteEntity toEntity(Note note)
|
private NoteEntity toEntity(Note note)
|
||||||
@@ -113,7 +122,7 @@ public class SQLNotes implements NoteRepository
|
|||||||
NoteEntity entity = new NoteEntity();
|
NoteEntity entity = new NoteEntity();
|
||||||
entity.setId(note.getId());
|
entity.setId(note.getId());
|
||||||
entity.setUuid(note.getUuid().toString());
|
entity.setUuid(note.getUuid().toString());
|
||||||
entity.setWrittenBy(note.getWrittenBy().toString());
|
entity.setWrittenByUuid(note.getWrittenBy().toString());
|
||||||
entity.setNote(note.getNote());
|
entity.setNote(note.getNote());
|
||||||
entity.setTimestamp(note.getTimestamp().toInstant().toEpochMilli());
|
entity.setTimestamp(note.getTimestamp().toInstant().toEpochMilli());
|
||||||
return entity;
|
return entity;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.j256.ormlite.dao.Dao;
|
|||||||
import com.j256.ormlite.dao.DaoManager;
|
import com.j256.ormlite.dao.DaoManager;
|
||||||
import com.j256.ormlite.support.ConnectionSource;
|
import com.j256.ormlite.support.ConnectionSource;
|
||||||
import com.j256.ormlite.stmt.UpdateBuilder;
|
import com.j256.ormlite.stmt.UpdateBuilder;
|
||||||
|
import dev.plex.api.punishment.PunishmentSource;
|
||||||
import dev.plex.punishment.Punishment;
|
import dev.plex.punishment.Punishment;
|
||||||
import dev.plex.punishment.PunishmentType;
|
import dev.plex.punishment.PunishmentType;
|
||||||
import dev.plex.storage.database.entity.PunishmentEntity;
|
import dev.plex.storage.database.entity.PunishmentEntity;
|
||||||
@@ -59,7 +60,7 @@ public class SQLPunishment implements PunishmentRepository
|
|||||||
{
|
{
|
||||||
try
|
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)
|
catch (SQLException e)
|
||||||
{
|
{
|
||||||
@@ -119,7 +120,7 @@ public class SQLPunishment implements PunishmentRepository
|
|||||||
{
|
{
|
||||||
UpdateBuilder<PunishmentEntity, Long> update = punishments.updateBuilder();
|
UpdateBuilder<PunishmentEntity, Long> update = punishments.updateBuilder();
|
||||||
update.updateColumnValue("active", active);
|
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();
|
update.update();
|
||||||
}
|
}
|
||||||
catch (SQLException e)
|
catch (SQLException e)
|
||||||
@@ -130,13 +131,13 @@ public class SQLPunishment implements PunishmentRepository
|
|||||||
|
|
||||||
private Punishment toPunishment(PunishmentEntity entity)
|
private Punishment toPunishment(PunishmentEntity entity)
|
||||||
{
|
{
|
||||||
UUID punisher = entity.getPunisher() == null || entity.getPunisher().isBlank() ? null : UUID.fromString(entity.getPunisher());
|
UUID punisher = entity.getPunisherUuid() == null || entity.getPunisherUuid().isBlank() ? null : UUID.fromString(entity.getPunisherUuid());
|
||||||
Punishment punishment = new Punishment(UUID.fromString(entity.getPunished()), punisher);
|
Punishment punishment = new Punishment(UUID.fromString(entity.getPunishedUuid()), punisher);
|
||||||
punishment.setActive(entity.isActive());
|
punishment.setActive(entity.isActive());
|
||||||
punishment.setType(PunishmentType.valueOf(entity.getType()));
|
punishment.setType(PunishmentType.valueOf(entity.getType()));
|
||||||
punishment.setCustomTime(entity.isCustomTime());
|
punishment.setCustomTime(entity.isCustomTime());
|
||||||
punishment.setPunishedUsername(entity.getPunishedUsername());
|
punishment.setSource(entity.getSource() == null ? punishment.getSource() : PunishmentSource.valueOf(entity.getSource()));
|
||||||
punishment.setPunisherName(entity.getPunisherName());
|
punishment.setPunisherReference(entity.getPunisherReference());
|
||||||
punishment.setIssueDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getIssueDate()), ZoneId.of(TimeUtils.TIMEZONE)));
|
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.setEndDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndDate()), ZoneId.of(TimeUtils.TIMEZONE)));
|
||||||
punishment.setReason(entity.getReason());
|
punishment.setReason(entity.getReason());
|
||||||
@@ -147,10 +148,11 @@ public class SQLPunishment implements PunishmentRepository
|
|||||||
private PunishmentEntity toEntity(Punishment punishment)
|
private PunishmentEntity toEntity(Punishment punishment)
|
||||||
{
|
{
|
||||||
PunishmentEntity entity = new PunishmentEntity();
|
PunishmentEntity entity = new PunishmentEntity();
|
||||||
entity.setPunished(punishment.getPunished().toString());
|
entity.setPunishedUuid(punishment.getPunished().toString());
|
||||||
entity.setPunisher(punishment.getPunisher() == null ? null : punishment.getPunisher().toString());
|
entity.setPunisherUuid(punishment.getPunisher() == null ? null : punishment.getPunisher().toString());
|
||||||
entity.setPunisherName(punishment.getPunisherName());
|
PunishmentSource source = punishment.getSource() == null ? (punishment.getPunisher() == null ? PunishmentSource.CONSOLE : PunishmentSource.PLAYER) : punishment.getSource();
|
||||||
entity.setPunishedUsername(punishment.getPunishedUsername());
|
entity.setSource(source.name());
|
||||||
|
entity.setPunisherReference(punishment.getPunisherReference());
|
||||||
entity.setIp(punishment.getIp());
|
entity.setIp(punishment.getIp());
|
||||||
entity.setType(punishment.getType().name());
|
entity.setType(punishment.getType().name());
|
||||||
entity.setReason(punishment.getReason());
|
entity.setReason(punishment.getReason());
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `players` (
|
CREATE TABLE IF NOT EXISTS `players` (
|
||||||
`uuid` VARCHAR(46) NOT NULL,
|
`uuid` VARCHAR(46) NOT NULL,
|
||||||
`name` VARCHAR(18),
|
`last_known_name` VARCHAR(18),
|
||||||
`login_msg` VARCHAR(2000),
|
`login_msg` VARCHAR(2000),
|
||||||
`prefix` VARCHAR(2000),
|
`prefix` VARCHAR(2000),
|
||||||
`staffChat` BOOLEAN,
|
`staffChat` BOOLEAN,
|
||||||
`ips` VARCHAR(2000),
|
`ips` VARCHAR(2000),
|
||||||
`coins` BIGINT,
|
|
||||||
`vanished` BOOLEAN,
|
|
||||||
`commandspy` BOOLEAN,
|
`commandspy` BOOLEAN,
|
||||||
PRIMARY KEY (`uuid`),
|
PRIMARY KEY (`uuid`),
|
||||||
INDEX `idx_players_name` (`name`)
|
INDEX `idx_players_last_known_name` (`last_known_name`)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `punishments` (
|
CREATE TABLE IF NOT EXISTS `punishments` (
|
||||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
`punished` VARCHAR(46) NOT NULL,
|
`punished_uuid` VARCHAR(46) NOT NULL,
|
||||||
`punisher` VARCHAR(46),
|
`punisher_uuid` VARCHAR(46),
|
||||||
`punisherName` VARCHAR(64),
|
`source` VARCHAR(30),
|
||||||
`punishedUsername` VARCHAR(16),
|
`punisher_reference` VARCHAR(200),
|
||||||
`ip` VARCHAR(2000),
|
`ip` VARCHAR(2000),
|
||||||
`type` VARCHAR(30),
|
`type` VARCHAR(30),
|
||||||
`reason` VARCHAR(2000),
|
`reason` VARCHAR(2000),
|
||||||
@@ -26,7 +24,7 @@ CREATE TABLE IF NOT EXISTS `punishments` (
|
|||||||
`issueDate` BIGINT NOT NULL,
|
`issueDate` BIGINT NOT NULL,
|
||||||
`endDate` BIGINT,
|
`endDate` BIGINT,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
INDEX `idx_punishments_punished` (`punished`),
|
INDEX `idx_punishments_punished` (`punished_uuid`),
|
||||||
INDEX `idx_punishments_ip` (`ip`(64))
|
INDEX `idx_punishments_ip` (`ip`(64))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -34,7 +32,7 @@ CREATE TABLE IF NOT EXISTS `notes` (
|
|||||||
`row_id` BIGINT NOT NULL AUTO_INCREMENT,
|
`row_id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
`id` INT NOT NULL,
|
`id` INT NOT NULL,
|
||||||
`uuid` VARCHAR(46) NOT NULL,
|
`uuid` VARCHAR(46) NOT NULL,
|
||||||
`written_by` VARCHAR(46),
|
`written_by_uuid` VARCHAR(46),
|
||||||
`note` VARCHAR(2000),
|
`note` VARCHAR(2000),
|
||||||
`timestamp` BIGINT,
|
`timestamp` BIGINT,
|
||||||
PRIMARY KEY (`row_id`),
|
PRIMARY KEY (`row_id`),
|
||||||
@@ -49,3 +47,12 @@ CREATE TABLE IF NOT EXISTS `player_ips` (
|
|||||||
UNIQUE KEY `uq_player_ips_player_ip` (`player_uuid`, `ip`),
|
UNIQUE KEY `uq_player_ips_player_ip` (`player_uuid`, `ip`),
|
||||||
INDEX `idx_player_ips_ip` (`ip`)
|
INDEX `idx_player_ips_ip` (`ip`)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `player_module_data` (
|
||||||
|
`player_uuid` VARCHAR(46) NOT NULL,
|
||||||
|
`module` VARCHAR(100) NOT NULL,
|
||||||
|
`data_key` VARCHAR(64) NOT NULL,
|
||||||
|
`value_json` LONGTEXT NOT NULL,
|
||||||
|
`updated_at` BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (`player_uuid`, `module`, `data_key`)
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
CREATE TABLE IF NOT EXISTS players (
|
CREATE TABLE IF NOT EXISTS players (
|
||||||
uuid VARCHAR(46) NOT NULL PRIMARY KEY,
|
uuid VARCHAR(46) NOT NULL PRIMARY KEY,
|
||||||
name VARCHAR(18),
|
last_known_name VARCHAR(18),
|
||||||
login_msg VARCHAR(2000),
|
login_msg VARCHAR(2000),
|
||||||
prefix VARCHAR(2000),
|
prefix VARCHAR(2000),
|
||||||
staffChat BOOLEAN,
|
staffChat BOOLEAN,
|
||||||
ips VARCHAR(2000),
|
ips VARCHAR(2000),
|
||||||
coins BIGINT,
|
|
||||||
vanished BOOLEAN,
|
|
||||||
commandspy BOOLEAN
|
commandspy BOOLEAN
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_players_name ON players(name);
|
CREATE INDEX IF NOT EXISTS idx_players_last_known_name ON players(last_known_name);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS punishments (
|
CREATE TABLE IF NOT EXISTS punishments (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
punished VARCHAR(46) NOT NULL,
|
punished_uuid VARCHAR(46) NOT NULL,
|
||||||
punisher VARCHAR(46),
|
punisher_uuid VARCHAR(46),
|
||||||
punisherName VARCHAR(64),
|
source VARCHAR(30),
|
||||||
punishedUsername VARCHAR(16),
|
punisher_reference VARCHAR(200),
|
||||||
ip VARCHAR(2000),
|
ip VARCHAR(2000),
|
||||||
type VARCHAR(30),
|
type VARCHAR(30),
|
||||||
reason VARCHAR(2000),
|
reason VARCHAR(2000),
|
||||||
@@ -27,14 +25,14 @@ CREATE TABLE IF NOT EXISTS punishments (
|
|||||||
endDate BIGINT
|
endDate BIGINT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_punishments_punished ON punishments(punished);
|
CREATE INDEX IF NOT EXISTS idx_punishments_punished ON punishments(punished_uuid);
|
||||||
CREATE INDEX IF NOT EXISTS idx_punishments_ip ON punishments(ip);
|
CREATE INDEX IF NOT EXISTS idx_punishments_ip ON punishments(ip);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS notes (
|
CREATE TABLE IF NOT EXISTS notes (
|
||||||
row_id BIGSERIAL PRIMARY KEY,
|
row_id BIGSERIAL PRIMARY KEY,
|
||||||
id INT NOT NULL,
|
id INT NOT NULL,
|
||||||
uuid VARCHAR(46) NOT NULL,
|
uuid VARCHAR(46) NOT NULL,
|
||||||
written_by VARCHAR(46),
|
written_by_uuid VARCHAR(46),
|
||||||
note VARCHAR(2000),
|
note VARCHAR(2000),
|
||||||
timestamp BIGINT
|
timestamp BIGINT
|
||||||
);
|
);
|
||||||
@@ -49,3 +47,12 @@ CREATE TABLE IF NOT EXISTS player_ips (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_player_ips_ip ON player_ips(ip);
|
CREATE INDEX IF NOT EXISTS idx_player_ips_ip ON player_ips(ip);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS player_module_data (
|
||||||
|
player_uuid VARCHAR(46) NOT NULL,
|
||||||
|
module VARCHAR(100) NOT NULL,
|
||||||
|
data_key VARCHAR(64) NOT NULL,
|
||||||
|
value_json TEXT NOT NULL,
|
||||||
|
updated_at BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (player_uuid, module, data_key)
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
CREATE TABLE IF NOT EXISTS players (
|
CREATE TABLE IF NOT EXISTS players (
|
||||||
uuid VARCHAR(46) NOT NULL PRIMARY KEY,
|
uuid VARCHAR(46) NOT NULL PRIMARY KEY,
|
||||||
name VARCHAR(18),
|
last_known_name VARCHAR(18),
|
||||||
login_msg VARCHAR(2000),
|
login_msg VARCHAR(2000),
|
||||||
prefix VARCHAR(2000),
|
prefix VARCHAR(2000),
|
||||||
staffChat BOOLEAN,
|
staffChat BOOLEAN,
|
||||||
ips VARCHAR(2000),
|
ips VARCHAR(2000),
|
||||||
coins BIGINT,
|
|
||||||
vanished BOOLEAN,
|
|
||||||
commandspy BOOLEAN
|
commandspy BOOLEAN
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS punishments (
|
CREATE TABLE IF NOT EXISTS punishments (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
punished VARCHAR(46) NOT NULL,
|
punished_uuid VARCHAR(46) NOT NULL,
|
||||||
punisher VARCHAR(46),
|
punisher_uuid VARCHAR(46),
|
||||||
punisherName VARCHAR(64),
|
source VARCHAR(30),
|
||||||
punishedUsername VARCHAR(16),
|
punisher_reference VARCHAR(200),
|
||||||
ip VARCHAR(2000),
|
ip VARCHAR(2000),
|
||||||
type VARCHAR(30),
|
type VARCHAR(30),
|
||||||
reason VARCHAR(2000),
|
reason VARCHAR(2000),
|
||||||
@@ -29,7 +27,7 @@ CREATE TABLE IF NOT EXISTS notes (
|
|||||||
row_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
row_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
id INT NOT NULL,
|
id INT NOT NULL,
|
||||||
uuid VARCHAR(46) NOT NULL,
|
uuid VARCHAR(46) NOT NULL,
|
||||||
written_by VARCHAR(46),
|
written_by_uuid VARCHAR(46),
|
||||||
note VARCHAR(2000),
|
note VARCHAR(2000),
|
||||||
timestamp BIGINT
|
timestamp BIGINT
|
||||||
);
|
);
|
||||||
@@ -40,8 +38,17 @@ CREATE TABLE IF NOT EXISTS player_ips (
|
|||||||
ip VARCHAR(64) NOT NULL
|
ip VARCHAR(64) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_players_name ON players(name);
|
CREATE TABLE IF NOT EXISTS player_module_data (
|
||||||
CREATE INDEX IF NOT EXISTS idx_punishments_punished ON punishments(punished);
|
player_uuid VARCHAR(46) NOT NULL,
|
||||||
|
module VARCHAR(100) NOT NULL,
|
||||||
|
data_key VARCHAR(64) NOT NULL,
|
||||||
|
value_json TEXT NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (player_uuid, module, data_key)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_players_last_known_name ON players(last_known_name);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_punishments_punished ON punishments(punished_uuid);
|
||||||
CREATE INDEX IF NOT EXISTS idx_punishments_ip ON punishments(ip);
|
CREATE INDEX IF NOT EXISTS idx_punishments_ip ON punishments(ip);
|
||||||
CREATE INDEX IF NOT EXISTS idx_notes_uuid ON notes(uuid);
|
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 UNIQUE INDEX IF NOT EXISTS uq_player_ips_player_ip ON player_ips(player_uuid, ip);
|
||||||
|
|||||||
Reference in New Issue
Block a user