From e1a6884db785f0eb36915fa4e5edd6ac9eda7a98 Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Thu, 28 May 2026 17:07:38 -0400 Subject: [PATCH] Update Guilds module (wip) --- src/main/java/dev/plex/Guilds.java | 4 +- .../java/dev/plex/command/GuildCommand.java | 14 +- .../plex/command/sub/AcceptSubCommand.java | 3 +- .../dev/plex/command/sub/ChatSubCommand.java | 3 +- .../plex/command/sub/CreateSubCommand.java | 3 +- .../dev/plex/command/sub/DenySubCommand.java | 3 +- .../plex/command/sub/DisbandSubCommand.java | 3 +- .../dev/plex/command/sub/GuildSubCommand.java | 28 + .../dev/plex/command/sub/HomeSubCommand.java | 3 +- .../dev/plex/command/sub/InfoSubCommand.java | 3 +- .../plex/command/sub/InviteSubCommand.java | 3 +- .../dev/plex/command/sub/LeaveSubCommand.java | 3 +- .../dev/plex/command/sub/OwnerSubCommand.java | 3 +- .../plex/command/sub/PrefixSubCommand.java | 3 +- .../plex/command/sub/SetHomeSubCommand.java | 3 +- .../plex/command/sub/SetWarpSubCommand.java | 3 +- .../plex/command/sub/WarpListSubCommand.java | 3 +- .../dev/plex/command/sub/WarpSubCommand.java | 3 +- .../dev/plex/storage/JdbiGuildRepository.java | 638 ++++++++++++++++++ .../dev/plex/storage/OrmGuildRepository.java | 372 ---------- .../dev/plex/storage/entity/GuildEntity.java | 30 - .../storage/entity/GuildInviteEntity.java | 12 - .../storage/entity/GuildMemberEntity.java | 12 - .../plex/storage/entity/GuildWarpEntity.java | 20 - .../migration/mariadb/001_initial_schema.sql | 2 +- .../migration/postgres/001_initial_schema.sql | 2 +- .../migration/sqlite/001_initial_schema.sql | 2 +- 27 files changed, 693 insertions(+), 488 deletions(-) create mode 100644 src/main/java/dev/plex/command/sub/GuildSubCommand.java create mode 100644 src/main/java/dev/plex/storage/JdbiGuildRepository.java delete mode 100644 src/main/java/dev/plex/storage/OrmGuildRepository.java diff --git a/src/main/java/dev/plex/Guilds.java b/src/main/java/dev/plex/Guilds.java index 59c5f55..cf6e98a 100644 --- a/src/main/java/dev/plex/Guilds.java +++ b/src/main/java/dev/plex/Guilds.java @@ -7,7 +7,7 @@ import dev.plex.handler.ChatHandlerImpl; import dev.plex.module.PlexModule; import dev.plex.api.storage.ModuleStorage; import dev.plex.storage.GuildRepository; -import dev.plex.storage.OrmGuildRepository; +import dev.plex.storage.JdbiGuildRepository; import lombok.Getter; import java.sql.SQLException; @@ -45,7 +45,7 @@ public class Guilds extends PlexModule { throw new IllegalStateException("Failed to run Guilds migrations", e); } - guildRepository = new OrmGuildRepository(storage); + guildRepository = new JdbiGuildRepository(storage); guildRepository.loadGuilds().whenComplete((guilds, throwable) -> { if (throwable != null) diff --git a/src/main/java/dev/plex/command/GuildCommand.java b/src/main/java/dev/plex/command/GuildCommand.java index 7df5228..5f43166 100644 --- a/src/main/java/dev/plex/command/GuildCommand.java +++ b/src/main/java/dev/plex/command/GuildCommand.java @@ -20,7 +20,7 @@ import java.util.Locale; public class GuildCommand extends SimplePlexCommand { - private final List subCommands = Lists.newArrayList(); + private final List subCommands = Lists.newArrayList(); public GuildCommand() { @@ -70,7 +70,7 @@ public class GuildCommand extends SimplePlexCommand .append(mmString("Permission: " + subCommand.getPermission())).append(Component.newline()) .append(mmString("Required Source: " + subCommand.getRequiredSource().name())); } - SimplePlexCommand subCommand = getSubCommand(args[0]); + GuildSubCommand subCommand = getSubCommand(args[0]); if (subCommand == null) { return messageComponent("guildCommandNotFound", args[0]); @@ -88,10 +88,10 @@ public class GuildCommand extends SimplePlexCommand checkPermission(commandSender, subCommand.getPermission()); - return subCommand.execute(commandSender, player, Arrays.copyOfRange(args, 1, args.length)); + return subCommand.executeSubCommand(commandSender, player, Arrays.copyOfRange(args, 1, args.length)); } - private SimplePlexCommand getSubCommand(String label) + private GuildSubCommand getSubCommand(String label) { return subCommands.stream() .filter(cmd -> cmd.getName().equalsIgnoreCase(label) || cmd.getAliases().stream().anyMatch(alias -> alias.equalsIgnoreCase(label))) @@ -99,7 +99,7 @@ public class GuildCommand extends SimplePlexCommand .orElse(null); } - private void registerSubCommand(SimplePlexCommand subCommand) + private void registerSubCommand(GuildSubCommand subCommand) { if (Guilds.get() != null) { @@ -132,10 +132,10 @@ public class GuildCommand extends SimplePlexCommand } if (args.length >= 2) { - SimplePlexCommand subCommand = getSubCommand(args[0]); + GuildSubCommand subCommand = getSubCommand(args[0]); if (subCommand != null) { - return subCommand.suggestions(sender, alias, Arrays.copyOfRange(args, 1, args.length)); + return subCommand.suggestSubCommand(sender, alias, Arrays.copyOfRange(args, 1, args.length)); } } return ImmutableList.of(); diff --git a/src/main/java/dev/plex/command/sub/AcceptSubCommand.java b/src/main/java/dev/plex/command/sub/AcceptSubCommand.java index fb8aa17..6ce718b 100644 --- a/src/main/java/dev/plex/command/sub/AcceptSubCommand.java +++ b/src/main/java/dev/plex/command/sub/AcceptSubCommand.java @@ -2,7 +2,6 @@ package dev.plex.command.sub; import com.google.common.collect.ImmutableList; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.guild.Guild; import dev.plex.guild.data.GuildRole; @@ -19,7 +18,7 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -public class AcceptSubCommand extends SimplePlexCommand +public class AcceptSubCommand extends GuildSubCommand { public AcceptSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/ChatSubCommand.java b/src/main/java/dev/plex/command/sub/ChatSubCommand.java index 7c02875..d4bf086 100644 --- a/src/main/java/dev/plex/command/sub/ChatSubCommand.java +++ b/src/main/java/dev/plex/command/sub/ChatSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.guild.data.Member; import java.util.Collections; @@ -16,7 +15,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class ChatSubCommand extends SimplePlexCommand +public class ChatSubCommand extends GuildSubCommand { public ChatSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/CreateSubCommand.java b/src/main/java/dev/plex/command/sub/CreateSubCommand.java index a27c516..29521b7 100644 --- a/src/main/java/dev/plex/command/sub/CreateSubCommand.java +++ b/src/main/java/dev/plex/command/sub/CreateSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.guild.Guild; import java.util.Collections; @@ -13,7 +12,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class CreateSubCommand extends SimplePlexCommand +public class CreateSubCommand extends GuildSubCommand { public CreateSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/DenySubCommand.java b/src/main/java/dev/plex/command/sub/DenySubCommand.java index 24fd4b1..cc61f0d 100644 --- a/src/main/java/dev/plex/command/sub/DenySubCommand.java +++ b/src/main/java/dev/plex/command/sub/DenySubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.storage.entity.GuildInviteEntity; import net.kyori.adventure.text.Component; @@ -12,7 +11,7 @@ import org.jetbrains.annotations.Nullable; import java.util.UUID; -public class DenySubCommand extends SimplePlexCommand +public class DenySubCommand extends GuildSubCommand { public DenySubCommand() { diff --git a/src/main/java/dev/plex/command/sub/DisbandSubCommand.java b/src/main/java/dev/plex/command/sub/DisbandSubCommand.java index 0eb8f86..e2ff300 100644 --- a/src/main/java/dev/plex/command/sub/DisbandSubCommand.java +++ b/src/main/java/dev/plex/command/sub/DisbandSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import net.kyori.adventure.text.Component; import org.bukkit.command.CommandSender; @@ -9,7 +8,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class DisbandSubCommand extends SimplePlexCommand +public class DisbandSubCommand extends GuildSubCommand { public DisbandSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/GuildSubCommand.java b/src/main/java/dev/plex/command/sub/GuildSubCommand.java new file mode 100644 index 0000000..1e19961 --- /dev/null +++ b/src/main/java/dev/plex/command/sub/GuildSubCommand.java @@ -0,0 +1,28 @@ +package dev.plex.command.sub; + +import dev.plex.command.CommandSpec; +import dev.plex.command.SimplePlexCommand; +import java.util.List; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class GuildSubCommand extends SimplePlexCommand +{ + protected GuildSubCommand(CommandSpec commandSpec) + { + super(commandSpec); + } + + public final Component executeSubCommand(@NotNull CommandSender sender, @Nullable Player player, @NotNull String[] args) + { + return execute(sender, player, args); + } + + public final @NotNull List suggestSubCommand(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) + { + return suggestions(sender, alias, args); + } +} diff --git a/src/main/java/dev/plex/command/sub/HomeSubCommand.java b/src/main/java/dev/plex/command/sub/HomeSubCommand.java index 21d2d1d..5f80080 100644 --- a/src/main/java/dev/plex/command/sub/HomeSubCommand.java +++ b/src/main/java/dev/plex/command/sub/HomeSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import java.util.Collections; import java.util.List; @@ -11,7 +10,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class HomeSubCommand extends SimplePlexCommand +public class HomeSubCommand extends GuildSubCommand { public HomeSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/InfoSubCommand.java b/src/main/java/dev/plex/command/sub/InfoSubCommand.java index 78538c8..888e89a 100644 --- a/src/main/java/dev/plex/command/sub/InfoSubCommand.java +++ b/src/main/java/dev/plex/command/sub/InfoSubCommand.java @@ -2,7 +2,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; import dev.plex.api.player.PlexPlayerView; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.guild.Guild; import java.time.format.DateTimeFormatter; @@ -16,7 +15,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class InfoSubCommand extends SimplePlexCommand +public class InfoSubCommand extends GuildSubCommand { public InfoSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/InviteSubCommand.java b/src/main/java/dev/plex/command/sub/InviteSubCommand.java index 6a2ae44..d5fed16 100644 --- a/src/main/java/dev/plex/command/sub/InviteSubCommand.java +++ b/src/main/java/dev/plex/command/sub/InviteSubCommand.java @@ -2,7 +2,6 @@ package dev.plex.command.sub; import com.google.common.collect.ImmutableList; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import net.kyori.adventure.text.Component; import org.bukkit.command.CommandSender; @@ -14,7 +13,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.List; -public class InviteSubCommand extends SimplePlexCommand +public class InviteSubCommand extends GuildSubCommand { public InviteSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/LeaveSubCommand.java b/src/main/java/dev/plex/command/sub/LeaveSubCommand.java index fb54ce6..ddbd7e5 100644 --- a/src/main/java/dev/plex/command/sub/LeaveSubCommand.java +++ b/src/main/java/dev/plex/command/sub/LeaveSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.guild.data.Member; import net.kyori.adventure.text.Component; @@ -12,7 +11,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; -public class LeaveSubCommand extends SimplePlexCommand +public class LeaveSubCommand extends GuildSubCommand { public LeaveSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/OwnerSubCommand.java b/src/main/java/dev/plex/command/sub/OwnerSubCommand.java index d9f904d..8fa0aa2 100644 --- a/src/main/java/dev/plex/command/sub/OwnerSubCommand.java +++ b/src/main/java/dev/plex/command/sub/OwnerSubCommand.java @@ -2,7 +2,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; import dev.plex.api.player.PlexPlayerView; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.guild.data.GuildRole; import dev.plex.guild.data.Member; @@ -14,7 +13,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class OwnerSubCommand extends SimplePlexCommand +public class OwnerSubCommand extends GuildSubCommand { public OwnerSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/PrefixSubCommand.java b/src/main/java/dev/plex/command/sub/PrefixSubCommand.java index 4e0866d..844df64 100644 --- a/src/main/java/dev/plex/command/sub/PrefixSubCommand.java +++ b/src/main/java/dev/plex/command/sub/PrefixSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.util.GuildUtil; import java.util.Collections; @@ -13,7 +12,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class PrefixSubCommand extends SimplePlexCommand +public class PrefixSubCommand extends GuildSubCommand { public PrefixSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/SetHomeSubCommand.java b/src/main/java/dev/plex/command/sub/SetHomeSubCommand.java index 1a63df8..d151662 100644 --- a/src/main/java/dev/plex/command/sub/SetHomeSubCommand.java +++ b/src/main/java/dev/plex/command/sub/SetHomeSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.util.CustomLocation; import java.util.Collections; @@ -12,7 +11,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class SetHomeSubCommand extends SimplePlexCommand +public class SetHomeSubCommand extends GuildSubCommand { public SetHomeSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/SetWarpSubCommand.java b/src/main/java/dev/plex/command/sub/SetWarpSubCommand.java index 1f4d37a..1220848 100644 --- a/src/main/java/dev/plex/command/sub/SetWarpSubCommand.java +++ b/src/main/java/dev/plex/command/sub/SetWarpSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.util.CustomLocation; import java.util.Collections; @@ -14,7 +13,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class SetWarpSubCommand extends SimplePlexCommand +public class SetWarpSubCommand extends GuildSubCommand { public SetWarpSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/WarpListSubCommand.java b/src/main/java/dev/plex/command/sub/WarpListSubCommand.java index 0dbd6bb..8b1ce63 100644 --- a/src/main/java/dev/plex/command/sub/WarpListSubCommand.java +++ b/src/main/java/dev/plex/command/sub/WarpListSubCommand.java @@ -2,7 +2,6 @@ package dev.plex.command.sub; import com.google.common.collect.Lists; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import dev.plex.guild.Guild; import java.util.Collections; @@ -14,7 +13,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class WarpListSubCommand extends SimplePlexCommand +public class WarpListSubCommand extends GuildSubCommand { public WarpListSubCommand() { diff --git a/src/main/java/dev/plex/command/sub/WarpSubCommand.java b/src/main/java/dev/plex/command/sub/WarpSubCommand.java index 6033e4b..34220b2 100644 --- a/src/main/java/dev/plex/command/sub/WarpSubCommand.java +++ b/src/main/java/dev/plex/command/sub/WarpSubCommand.java @@ -1,7 +1,6 @@ package dev.plex.command.sub; import dev.plex.Guilds; -import dev.plex.command.SimplePlexCommand; import dev.plex.command.source.RequiredCommandSource; import java.util.Collections; import java.util.List; @@ -12,7 +11,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class WarpSubCommand extends SimplePlexCommand +public class WarpSubCommand extends GuildSubCommand { public WarpSubCommand() { diff --git a/src/main/java/dev/plex/storage/JdbiGuildRepository.java b/src/main/java/dev/plex/storage/JdbiGuildRepository.java new file mode 100644 index 0000000..da61a74 --- /dev/null +++ b/src/main/java/dev/plex/storage/JdbiGuildRepository.java @@ -0,0 +1,638 @@ +package dev.plex.storage; + +import dev.plex.Guilds; +import dev.plex.api.storage.ModuleStorage; +import dev.plex.guild.Guild; +import dev.plex.guild.data.GuildRole; +import dev.plex.guild.data.Member; +import dev.plex.storage.entity.GuildEntity; +import dev.plex.storage.entity.GuildInviteEntity; +import dev.plex.storage.entity.GuildMemberEntity; +import dev.plex.storage.entity.GuildWarpEntity; +import dev.plex.util.CustomLocation; +import org.bukkit.entity.Player; +import org.jdbi.v3.core.Handle; +import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.core.JdbiException; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +public class JdbiGuildRepository implements GuildRepository +{ + private final Jdbi jdbi; + private final Executor executor; + private final String guildsTable; + private final String membersTable; + private final String warpsTable; + private final String invitesTable; + + public JdbiGuildRepository(ModuleStorage storage) + { + this.jdbi = storage.jdbi(); + this.executor = Guilds.get().api().scheduler().asyncExecutor(); + this.guildsTable = storage.table("guilds"); + this.membersTable = storage.table("members"); + this.warpsTable = storage.table("warps"); + this.invitesTable = storage.table("invites"); + ensureSchema(); + } + + @Override + public CompletableFuture> loadGuilds() + { + return CompletableFuture.supplyAsync(() -> + { + try + { + return jdbi.withHandle(h -> + { + List guilds = h.createQuery("SELECT * FROM " + guildsTable) + .map((rs, ctx) -> guildMapRow(rs)).list(); + Map> membersByGuild = h.createQuery("SELECT * FROM " + membersTable) + .map((rs, ctx) -> memberMapRow(rs)).list().stream() + .collect(Collectors.groupingBy(GuildMemberEntity::getGuildUuid)); + Map> warpsByGuild = h.createQuery("SELECT * FROM " + warpsTable) + .map((rs, ctx) -> warpMapRow(rs)).list().stream() + .collect(Collectors.groupingBy(GuildWarpEntity::getGuildUuid)); + return guilds.stream().map(entity -> + { + Guild guild = toGuildBase(entity); + membersByGuild.getOrDefault(entity.getGuildUuid(), List.of()) + .forEach(member -> guild.addMember(toMember(member))); + warpsByGuild.getOrDefault(entity.getGuildUuid(), List.of()) + .forEach(warp -> guild.getWarps().put(warp.getName(), toLocation(warp))); + return guild; + }).toList(); + }); + } + catch (JdbiException e) + { + throw new IllegalStateException("Failed to load guilds", e); + } + }, executor); + } + + @Override + public CompletableFuture createGuild(Player owner, String name) + { + return CompletableFuture.supplyAsync(() -> + { + try + { + Guild guild = Guild.create(owner, name); + GuildEntity e = toEntity(guild); + jdbi.useTransaction(h -> + { + h.createUpdate("INSERT INTO " + guildsTable + " (guild_uuid, name, prefix, owner_uuid, created_at, " + + "home_world, home_x, home_y, home_z, home_yaw, home_pitch, motd, tag_enabled, is_public) " + + "VALUES (:guildUuid, :name, :prefix, :ownerUuid, :createdAt, :homeWorld, :homeX, :homeY, :homeZ, " + + ":homeYaw, :homePitch, :motd, :tagEnabled, :isPublic)") + .bind("guildUuid", e.getGuildUuid()) + .bind("name", e.getName()) + .bind("prefix", e.getPrefix()) + .bind("ownerUuid", e.getOwnerUuid()) + .bind("createdAt", e.getCreatedAt()) + .bind("homeWorld", e.getHomeWorld()) + .bind("homeX", e.getHomeX()) + .bind("homeY", e.getHomeY()) + .bind("homeZ", e.getHomeZ()) + .bind("homeYaw", e.getHomeYaw()) + .bind("homePitch", e.getHomePitch()) + .bind("motd", e.getMotd()) + .bind("tagEnabled", e.isTagEnabled()) + .bind("isPublic", e.isPublicGuild()) + .execute(); + insertMember(h, guild.getGuildUuid(), owner.getUniqueId(), GuildRole.OWNER); + }); + return guild; + } + catch (JdbiException e) + { + throw new IllegalStateException("Failed to create guild", e); + } + }, executor); + } + + @Override + public CompletableFuture deleteGuild(UUID guildUuid) + { + return runAsync(() -> jdbi.useTransaction(h -> + { + h.createUpdate("DELETE FROM " + membersTable + " WHERE guild_uuid = :g").bind("g", guildUuid.toString()).execute(); + h.createUpdate("DELETE FROM " + warpsTable + " WHERE guild_uuid = :g").bind("g", guildUuid.toString()).execute(); + h.createUpdate("DELETE FROM " + invitesTable + " WHERE guild_uuid = :g").bind("g", guildUuid.toString()).execute(); + h.createUpdate("DELETE FROM " + guildsTable + " WHERE guild_uuid = :g").bind("g", guildUuid.toString()).execute(); + })); + } + + @Override + public CompletableFuture addMember(UUID guildUuid, UUID playerUuid, GuildRole role) + { + return runAsync(() -> jdbi.useTransaction(h -> upsertMember(h, guildUuid, playerUuid, role))); + } + + @Override + public CompletableFuture removeMember(UUID guildUuid, UUID playerUuid) + { + return runAsync(() -> jdbi.useHandle(h -> h.createUpdate("DELETE FROM " + membersTable + " WHERE guild_uuid = :g AND player_uuid = :p") + .bind("g", guildUuid.toString()) + .bind("p", playerUuid.toString()) + .execute())); + } + + @Override + public CompletableFuture transferOwner(UUID guildUuid, UUID newOwnerUuid, UUID oldOwnerUuid) + { + return runAsync(() -> jdbi.useTransaction(h -> + { + h.createUpdate("UPDATE " + guildsTable + " SET owner_uuid = :o WHERE guild_uuid = :g") + .bind("o", newOwnerUuid.toString()) + .bind("g", guildUuid.toString()) + .execute(); + upsertMember(h, guildUuid, oldOwnerUuid, GuildRole.MEMBER); + upsertMember(h, guildUuid, newOwnerUuid, GuildRole.OWNER); + })); + } + + @Override + public CompletableFuture updatePrefix(UUID guildUuid, String prefix) + { + return runAsync(() -> jdbi.useHandle(h -> h.createUpdate("UPDATE " + guildsTable + " SET prefix = :p WHERE guild_uuid = :g") + .bind("p", prefix) + .bind("g", guildUuid.toString()) + .execute())); + } + + @Override + public CompletableFuture updateHome(UUID guildUuid, CustomLocation home) + { + return runAsync(() -> jdbi.useHandle(h -> h.createUpdate("UPDATE " + guildsTable + + " SET home_world = :w, home_x = :x, home_y = :y, home_z = :z, home_yaw = :yaw, home_pitch = :pitch " + + "WHERE guild_uuid = :g") + .bind("w", home == null ? null : home.getWorldName()) + .bind("x", home == null ? null : home.getX()) + .bind("y", home == null ? null : home.getY()) + .bind("z", home == null ? null : home.getZ()) + .bind("yaw", home == null ? null : home.getYaw()) + .bind("pitch", home == null ? null : home.getPitch()) + .bind("g", guildUuid.toString()) + .execute())); + } + + @Override + public CompletableFuture upsertWarp(UUID guildUuid, String name, CustomLocation location) + { + return runAsync(() -> jdbi.useTransaction(h -> + { + String lowerName = name.toLowerCase(Locale.ROOT); + GuildWarpEntity entity = h.createQuery("SELECT * FROM " + warpsTable + " WHERE guild_uuid = :g AND name = :n") + .bind("g", guildUuid.toString()) + .bind("n", lowerName) + .map((rs, ctx) -> warpMapRow(rs)) + .findFirst() + .orElse(null); + if (entity == null) + { + entity = new GuildWarpEntity(); + entity.setGuildUuid(guildUuid.toString()); + entity.setName(lowerName); + setWarpLocation(entity, location); + h.createUpdate("INSERT INTO " + warpsTable + " (guild_uuid, name, world, x, y, z, yaw, pitch) " + + "VALUES (:g, :n, :w, :x, :y, :z, :yaw, :pitch)") + .bind("g", entity.getGuildUuid()) + .bind("n", entity.getName()) + .bind("w", entity.getWorld()) + .bind("x", entity.getX()) + .bind("y", entity.getY()) + .bind("z", entity.getZ()) + .bind("yaw", entity.getYaw()) + .bind("pitch", entity.getPitch()) + .execute(); + return; + } + setWarpLocation(entity, location); + h.createUpdate("UPDATE " + warpsTable + " SET world = :w, x = :x, y = :y, z = :z, yaw = :yaw, pitch = :pitch " + + "WHERE guild_uuid = :g AND name = :n") + .bind("w", entity.getWorld()) + .bind("x", entity.getX()) + .bind("y", entity.getY()) + .bind("z", entity.getZ()) + .bind("yaw", entity.getYaw()) + .bind("pitch", entity.getPitch()) + .bind("g", guildUuid.toString()) + .bind("n", lowerName) + .execute(); + })); + } + + @Override + public CompletableFuture deleteWarp(UUID guildUuid, String name) + { + return runAsync(() -> jdbi.useHandle(h -> h.createUpdate("DELETE FROM " + warpsTable + " WHERE guild_uuid = :g AND name = :n") + .bind("g", guildUuid.toString()) + .bind("n", name.toLowerCase(Locale.ROOT)) + .execute())); + } + + @Override + public CompletableFuture createInvite(UUID guildUuid, UUID inviterUuid, UUID inviteeUuid, Instant expiresAt) + { + return runAsync(() -> jdbi.useTransaction(h -> + { + deleteInviteSync(h, guildUuid, inviteeUuid); + h.createUpdate("INSERT INTO " + invitesTable + " (guild_uuid, inviter_uuid, invitee_uuid, expires_at) " + + "VALUES (:g, :inviter, :invitee, :expires)") + .bind("g", guildUuid.toString()) + .bind("inviter", inviterUuid.toString()) + .bind("invitee", inviteeUuid.toString()) + .bind("expires", expiresAt.toEpochMilli()) + .execute(); + })); + } + + @Override + public CompletableFuture deleteInvite(UUID guildUuid, UUID inviteeUuid) + { + return runAsync(() -> jdbi.useHandle(h -> deleteInviteSync(h, guildUuid, inviteeUuid))); + } + + @Override + public CompletableFuture> invitesFor(UUID inviteeUuid) + { + return CompletableFuture.supplyAsync(() -> + { + try + { + long now = Instant.now().toEpochMilli(); + return jdbi.withHandle(h -> h.createQuery("SELECT * FROM " + invitesTable + " WHERE invitee_uuid = :i") + .bind("i", inviteeUuid.toString()) + .map((rs, ctx) -> inviteMapRow(rs)) + .list()).stream() + .filter(invite -> invite.getExpiresAt() >= now) + .toList(); + } + catch (JdbiException e) + { + throw new IllegalStateException("Failed to load guild invites", e); + } + }, executor); + } + + private static GuildEntity guildMapRow(java.sql.ResultSet rs) throws java.sql.SQLException + { + GuildEntity e = new GuildEntity(); + e.setGuildUuid(rs.getString("guild_uuid")); + e.setName(rs.getString("name")); + e.setPrefix(rs.getString("prefix")); + e.setOwnerUuid(rs.getString("owner_uuid")); + e.setCreatedAt(rs.getLong("created_at")); + e.setHomeWorld(rs.getString("home_world")); + e.setHomeX(rs.getObject("home_x", Double.class)); + e.setHomeY(rs.getObject("home_y", Double.class)); + e.setHomeZ(rs.getObject("home_z", Double.class)); + e.setHomeYaw(rs.getObject("home_yaw", Float.class)); + e.setHomePitch(rs.getObject("home_pitch", Float.class)); + e.setMotd(rs.getString("motd")); + e.setTagEnabled(rs.getBoolean("tag_enabled")); + e.setPublicGuild(rs.getBoolean("is_public")); + return e; + } + + private static GuildMemberEntity memberMapRow(java.sql.ResultSet rs) throws java.sql.SQLException + { + GuildMemberEntity e = new GuildMemberEntity(); + e.setId(rs.getLong("id")); + e.setGuildUuid(rs.getString("guild_uuid")); + e.setPlayerUuid(rs.getString("player_uuid")); + e.setRole(rs.getString("role")); + e.setJoinedAt(rs.getLong("joined_at")); + return e; + } + + private static GuildWarpEntity warpMapRow(java.sql.ResultSet rs) throws java.sql.SQLException + { + GuildWarpEntity e = new GuildWarpEntity(); + e.setId(rs.getLong("id")); + e.setGuildUuid(rs.getString("guild_uuid")); + e.setName(rs.getString("name")); + e.setWorld(rs.getString("world")); + e.setX(rs.getDouble("x")); + e.setY(rs.getDouble("y")); + e.setZ(rs.getDouble("z")); + e.setYaw(rs.getFloat("yaw")); + e.setPitch(rs.getFloat("pitch")); + return e; + } + + private static GuildInviteEntity inviteMapRow(java.sql.ResultSet rs) throws java.sql.SQLException + { + GuildInviteEntity e = new GuildInviteEntity(); + e.setId(rs.getLong("id")); + e.setGuildUuid(rs.getString("guild_uuid")); + e.setInviterUuid(rs.getString("inviter_uuid")); + e.setInviteeUuid(rs.getString("invitee_uuid")); + e.setExpiresAt(rs.getLong("expires_at")); + return e; + } + + private void ensureSchema() + { + try + { + jdbi.useHandle(h -> + { + String productName = h.getConnection().getMetaData().getDatabaseProductName().toLowerCase(Locale.ROOT); + schemaStatements(productName).forEach(sql -> h.createUpdate(sql).execute()); + }); + } + catch (Exception e) + { + throw new IllegalStateException("Failed to create guild tables", e); + } + } + + private List schemaStatements(String productName) + { + if (productName.contains("sqlite")) + { + return List.of( + "CREATE TABLE IF NOT EXISTS " + guildsTable + " (" + + "guild_uuid VARCHAR(46) NOT NULL PRIMARY KEY, " + + "name VARCHAR(64) NOT NULL UNIQUE, " + + "prefix VARCHAR(64), " + + "owner_uuid VARCHAR(46) NOT NULL, " + + "created_at BIGINT NOT NULL, " + + "home_world VARCHAR(128), " + + "home_x DOUBLE, " + + "home_y DOUBLE, " + + "home_z DOUBLE, " + + "home_yaw FLOAT, " + + "home_pitch FLOAT, " + + "motd VARCHAR(3000), " + + "tag_enabled BOOLEAN NOT NULL DEFAULT 1, " + + "is_public BOOLEAN NOT NULL DEFAULT 0" + + ")", + "CREATE TABLE IF NOT EXISTS " + membersTable + " (" + + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "guild_uuid VARCHAR(46) NOT NULL, " + + "player_uuid VARCHAR(46) NOT NULL, " + + "role VARCHAR(20) NOT NULL, " + + "joined_at BIGINT NOT NULL, " + + "UNIQUE (guild_uuid, player_uuid)" + + ")", + "CREATE TABLE IF NOT EXISTS " + warpsTable + " (" + + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "guild_uuid VARCHAR(46) NOT NULL, " + + "name VARCHAR(16) NOT NULL, " + + "world VARCHAR(128) NOT NULL, " + + "x DOUBLE NOT NULL, " + + "y DOUBLE NOT NULL, " + + "z DOUBLE NOT NULL, " + + "yaw FLOAT NOT NULL, " + + "pitch FLOAT NOT NULL, " + + "UNIQUE (guild_uuid, name)" + + ")", + "CREATE TABLE IF NOT EXISTS " + invitesTable + " (" + + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "guild_uuid VARCHAR(46) NOT NULL, " + + "inviter_uuid VARCHAR(46) NOT NULL, " + + "invitee_uuid VARCHAR(46) NOT NULL, " + + "expires_at BIGINT NOT NULL, " + + "UNIQUE (guild_uuid, invitee_uuid)" + + ")" + ); + } + if (productName.contains("postgres")) + { + return List.of( + "CREATE TABLE IF NOT EXISTS " + guildsTable + " (" + + "guild_uuid VARCHAR(46) NOT NULL PRIMARY KEY, " + + "name VARCHAR(64) NOT NULL UNIQUE, " + + "prefix VARCHAR(64), " + + "owner_uuid VARCHAR(46) NOT NULL, " + + "created_at BIGINT NOT NULL, " + + "home_world VARCHAR(128), " + + "home_x DOUBLE PRECISION, " + + "home_y DOUBLE PRECISION, " + + "home_z DOUBLE PRECISION, " + + "home_yaw REAL, " + + "home_pitch REAL, " + + "motd VARCHAR(3000), " + + "tag_enabled BOOLEAN NOT NULL DEFAULT TRUE, " + + "is_public BOOLEAN NOT NULL DEFAULT FALSE" + + ")", + "CREATE TABLE IF NOT EXISTS " + membersTable + " (" + + "id BIGSERIAL PRIMARY KEY, " + + "guild_uuid VARCHAR(46) NOT NULL, " + + "player_uuid VARCHAR(46) NOT NULL, " + + "role VARCHAR(20) NOT NULL, " + + "joined_at BIGINT NOT NULL, " + + "CONSTRAINT uq_members_guild_player UNIQUE (guild_uuid, player_uuid)" + + ")", + "CREATE TABLE IF NOT EXISTS " + warpsTable + " (" + + "id BIGSERIAL PRIMARY KEY, " + + "guild_uuid VARCHAR(46) NOT NULL, " + + "name VARCHAR(16) NOT NULL, " + + "world VARCHAR(128) NOT NULL, " + + "x DOUBLE PRECISION NOT NULL, " + + "y DOUBLE PRECISION NOT NULL, " + + "z DOUBLE PRECISION NOT NULL, " + + "yaw REAL NOT NULL, " + + "pitch REAL NOT NULL, " + + "CONSTRAINT uq_warps_guild_name UNIQUE (guild_uuid, name)" + + ")", + "CREATE TABLE IF NOT EXISTS " + invitesTable + " (" + + "id BIGSERIAL PRIMARY KEY, " + + "guild_uuid VARCHAR(46) NOT NULL, " + + "inviter_uuid VARCHAR(46) NOT NULL, " + + "invitee_uuid VARCHAR(46) NOT NULL, " + + "expires_at BIGINT NOT NULL, " + + "CONSTRAINT uq_invites_guild_invitee UNIQUE (guild_uuid, invitee_uuid)" + + ")" + ); + } + return List.of( + "CREATE TABLE IF NOT EXISTS " + guildsTable + " (" + + "`guild_uuid` VARCHAR(46) NOT NULL, " + + "`name` VARCHAR(64) NOT NULL, " + + "`prefix` VARCHAR(64), " + + "`owner_uuid` VARCHAR(46) NOT NULL, " + + "`created_at` BIGINT NOT NULL, " + + "`home_world` VARCHAR(128), " + + "`home_x` DOUBLE, " + + "`home_y` DOUBLE, " + + "`home_z` DOUBLE, " + + "`home_yaw` FLOAT, " + + "`home_pitch` FLOAT, " + + "`motd` VARCHAR(3000), " + + "`tag_enabled` BOOLEAN NOT NULL DEFAULT TRUE, " + + "`is_public` BOOLEAN NOT NULL DEFAULT FALSE, " + + "PRIMARY KEY (`guild_uuid`), " + + "UNIQUE KEY `uq_guilds_name` (`name`)" + + ")", + "CREATE TABLE IF NOT EXISTS " + membersTable + " (" + + "`id` BIGINT NOT NULL AUTO_INCREMENT, " + + "`guild_uuid` VARCHAR(46) NOT NULL, " + + "`player_uuid` VARCHAR(46) NOT NULL, " + + "`role` VARCHAR(20) NOT NULL, " + + "`joined_at` BIGINT NOT NULL, " + + "PRIMARY KEY (`id`), " + + "UNIQUE KEY `uq_members_guild_player` (`guild_uuid`, `player_uuid`)" + + ")", + "CREATE TABLE IF NOT EXISTS " + warpsTable + " (" + + "`id` BIGINT NOT NULL AUTO_INCREMENT, " + + "`guild_uuid` VARCHAR(46) NOT NULL, " + + "`name` VARCHAR(16) NOT NULL, " + + "`world` VARCHAR(128) NOT NULL, " + + "`x` DOUBLE NOT NULL, " + + "`y` DOUBLE NOT NULL, " + + "`z` DOUBLE NOT NULL, " + + "`yaw` FLOAT NOT NULL, " + + "`pitch` FLOAT NOT NULL, " + + "PRIMARY KEY (`id`), " + + "UNIQUE KEY `uq_warps_guild_name` (`guild_uuid`, `name`)" + + ")", + "CREATE TABLE IF NOT EXISTS " + invitesTable + " (" + + "`id` BIGINT NOT NULL AUTO_INCREMENT, " + + "`guild_uuid` VARCHAR(46) NOT NULL, " + + "`inviter_uuid` VARCHAR(46) NOT NULL, " + + "`invitee_uuid` VARCHAR(46) NOT NULL, " + + "`expires_at` BIGINT NOT NULL, " + + "PRIMARY KEY (`id`), " + + "UNIQUE KEY `uq_invites_guild_invitee` (`guild_uuid`, `invitee_uuid`)" + + ")" + ); + } + + private Guild toGuildBase(GuildEntity entity) + { + String timezone = Guilds.get().api().configuration().mainConfig().getString("server.timezone", "Etc/UTC"); + Guild guild = new Guild(UUID.fromString(entity.getGuildUuid()), ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getCreatedAt()), ZoneId.of(timezone))); + guild.setName(entity.getName()); + guild.setOwnerUuid(UUID.fromString(entity.getOwnerUuid())); + guild.setPrefix(entity.getPrefix()); + guild.setMotd(entity.getMotd()); + guild.setTagEnabled(entity.isTagEnabled()); + guild.setPublic(entity.isPublicGuild()); + guild.setHome(toLocation(entity)); + return guild; + } + + private GuildEntity toEntity(Guild guild) + { + GuildEntity entity = new GuildEntity(); + entity.setGuildUuid(guild.getGuildUuid().toString()); + entity.setName(guild.getName()); + entity.setOwnerUuid(guild.getOwnerUuid().toString()); + entity.setCreatedAt(guild.getCreatedAt().toInstant().toEpochMilli()); + entity.setPrefix(guild.getPrefix()); + entity.setMotd(guild.getMotd()); + entity.setTagEnabled(guild.isTagEnabled()); + entity.setPublicGuild(guild.isPublic()); + setHome(entity, guild.getHome()); + return entity; + } + + private Member toMember(GuildMemberEntity entity) + { + return new Member(UUID.fromString(entity.getPlayerUuid()), GuildRole.valueOf(entity.getRole())); + } + + private GuildMemberEntity memberEntity(UUID guildUuid, UUID playerUuid, GuildRole role) + { + GuildMemberEntity entity = new GuildMemberEntity(); + entity.setGuildUuid(guildUuid.toString()); + entity.setPlayerUuid(playerUuid.toString()); + entity.setRole(role.name()); + entity.setJoinedAt(Instant.now().toEpochMilli()); + return entity; + } + + private void insertMember(Handle h, UUID guildUuid, UUID playerUuid, GuildRole role) + { + GuildMemberEntity entity = memberEntity(guildUuid, playerUuid, role); + h.createUpdate("INSERT INTO " + membersTable + " (guild_uuid, player_uuid, role, joined_at) " + + "VALUES (:g, :p, :r, :j)") + .bind("g", entity.getGuildUuid()) + .bind("p", entity.getPlayerUuid()) + .bind("r", entity.getRole()) + .bind("j", entity.getJoinedAt()) + .execute(); + } + + private void upsertMember(Handle h, UUID guildUuid, UUID playerUuid, GuildRole role) + { + GuildMemberEntity existing = h.createQuery("SELECT * FROM " + membersTable + " WHERE guild_uuid = :g AND player_uuid = :p") + .bind("g", guildUuid.toString()) + .bind("p", playerUuid.toString()) + .map((rs, ctx) -> memberMapRow(rs)) + .findFirst() + .orElse(null); + if (existing == null) + { + insertMember(h, guildUuid, playerUuid, role); + return; + } + h.createUpdate("UPDATE " + membersTable + " SET role = :r WHERE guild_uuid = :g AND player_uuid = :p") + .bind("r", role.name()) + .bind("g", guildUuid.toString()) + .bind("p", playerUuid.toString()) + .execute(); + } + + private void deleteInviteSync(Handle h, UUID guildUuid, UUID inviteeUuid) + { + h.createUpdate("DELETE FROM " + invitesTable + " WHERE guild_uuid = :g AND invitee_uuid = :i") + .bind("g", guildUuid.toString()) + .bind("i", inviteeUuid.toString()) + .execute(); + } + + private void setHome(GuildEntity entity, CustomLocation home) + { + entity.setHomeWorld(home == null ? null : home.getWorldName()); + entity.setHomeX(home == null ? null : home.getX()); + entity.setHomeY(home == null ? null : home.getY()); + entity.setHomeZ(home == null ? null : home.getZ()); + entity.setHomeYaw(home == null ? null : home.getYaw()); + entity.setHomePitch(home == null ? null : home.getPitch()); + } + + private CustomLocation toLocation(GuildEntity entity) + { + if (entity.getHomeWorld() == null) + { + return null; + } + return new CustomLocation(entity.getHomeWorld(), entity.getHomeX(), entity.getHomeY(), entity.getHomeZ(), entity.getHomeYaw(), entity.getHomePitch()); + } + + private void setWarpLocation(GuildWarpEntity entity, CustomLocation location) + { + entity.setWorld(location.getWorldName()); + entity.setX(location.getX()); + entity.setY(location.getY()); + entity.setZ(location.getZ()); + entity.setYaw(location.getYaw()); + entity.setPitch(location.getPitch()); + } + + private CustomLocation toLocation(GuildWarpEntity entity) + { + return new CustomLocation(entity.getWorld(), entity.getX(), entity.getY(), entity.getZ(), entity.getYaw(), entity.getPitch()); + } + + private CompletableFuture runAsync(Runnable runnable) + { + return CompletableFuture.runAsync(runnable, executor); + } +} diff --git a/src/main/java/dev/plex/storage/OrmGuildRepository.java b/src/main/java/dev/plex/storage/OrmGuildRepository.java deleted file mode 100644 index 36d88be..0000000 --- a/src/main/java/dev/plex/storage/OrmGuildRepository.java +++ /dev/null @@ -1,372 +0,0 @@ -package dev.plex.storage; - -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.stmt.DeleteBuilder; -import dev.plex.Guilds; -import dev.plex.api.storage.ModuleStorage; -import dev.plex.guild.Guild; -import dev.plex.guild.data.GuildRole; -import dev.plex.guild.data.Member; -import dev.plex.storage.entity.GuildEntity; -import dev.plex.storage.entity.GuildInviteEntity; -import dev.plex.storage.entity.GuildMemberEntity; -import dev.plex.storage.entity.GuildWarpEntity; -import dev.plex.util.CustomLocation; -import org.bukkit.entity.Player; - -import java.sql.SQLException; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Locale; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; - -public class OrmGuildRepository implements GuildRepository -{ - private final ModuleStorage moduleStorage; - private final Executor executor; - private final Dao guilds; - private final Dao members; - private final Dao warps; - private final Dao invites; - - public OrmGuildRepository(ModuleStorage moduleStorage) - { - this.moduleStorage = moduleStorage; - this.executor = Guilds.get().api().scheduler().asyncExecutor(); - try - { - this.guilds = moduleStorage.orm().dao(GuildEntity.class, "guilds"); - this.members = moduleStorage.orm().dao(GuildMemberEntity.class, "members"); - this.warps = moduleStorage.orm().dao(GuildWarpEntity.class, "warps"); - this.invites = moduleStorage.orm().dao(GuildInviteEntity.class, "invites"); - } - catch (SQLException e) - { - throw new IllegalStateException("Failed to create guild DAOs", e); - } - } - - @Override - public CompletableFuture> loadGuilds() - { - return CompletableFuture.supplyAsync(() -> - { - try - { - return guilds.queryForAll().stream().map(this::toGuild).toList(); - } - catch (SQLException e) - { - throw new IllegalStateException("Failed to load guilds", e); - } - }, executor); - } - - @Override - public CompletableFuture createGuild(Player owner, String name) - { - return CompletableFuture.supplyAsync(() -> - { - try - { - Guild guild = Guild.create(owner, name); - moduleStorage.transaction(() -> - { - guilds.create(toEntity(guild)); - members.create(memberEntity(guild.getGuildUuid(), owner.getUniqueId(), GuildRole.OWNER)); - return null; - }); - return guild; - } - catch (SQLException e) - { - throw new IllegalStateException("Failed to create guild", e); - } - }, executor); - } - - @Override - public CompletableFuture deleteGuild(UUID guildUuid) - { - return runAsync(() -> moduleStorage.transaction(() -> - { - deleteByGuild(members, guildUuid); - deleteByGuild(warps, guildUuid); - deleteByGuild(invites, guildUuid); - guilds.deleteById(guildUuid.toString()); - return null; - })); - } - - @Override - public CompletableFuture addMember(UUID guildUuid, UUID playerUuid, GuildRole role) - { - return runAsync(() -> upsertMember(guildUuid, playerUuid, role)); - } - - @Override - public CompletableFuture removeMember(UUID guildUuid, UUID playerUuid) - { - return runAsync(() -> - { - DeleteBuilder delete = members.deleteBuilder(); - delete.where().eq("guild_uuid", guildUuid.toString()).and().eq("player_uuid", playerUuid.toString()); - delete.delete(); - }); - } - - @Override - public CompletableFuture transferOwner(UUID guildUuid, UUID newOwnerUuid, UUID oldOwnerUuid) - { - return runAsync(() -> moduleStorage.transaction(() -> - { - GuildEntity guild = guilds.queryForId(guildUuid.toString()); - if (guild != null) - { - guild.setOwnerUuid(newOwnerUuid.toString()); - guilds.update(guild); - } - upsertMember(guildUuid, oldOwnerUuid, GuildRole.MEMBER); - upsertMember(guildUuid, newOwnerUuid, GuildRole.OWNER); - return null; - })); - } - - @Override - public CompletableFuture updatePrefix(UUID guildUuid, String prefix) - { - return runAsync(() -> - { - GuildEntity guild = guilds.queryForId(guildUuid.toString()); - if (guild != null) - { - guild.setPrefix(prefix); - guilds.update(guild); - } - }); - } - - @Override - public CompletableFuture updateHome(UUID guildUuid, CustomLocation home) - { - return runAsync(() -> - { - GuildEntity guild = guilds.queryForId(guildUuid.toString()); - if (guild != null) - { - setHome(guild, home); - guilds.update(guild); - } - }); - } - - @Override - public CompletableFuture upsertWarp(UUID guildUuid, String name, CustomLocation location) - { - return runAsync(() -> - { - GuildWarpEntity entity = warps.queryBuilder().where() - .eq("guild_uuid", guildUuid.toString()).and().eq("name", name.toLowerCase(Locale.ROOT)) - .queryForFirst(); - if (entity == null) - { - entity = new GuildWarpEntity(); - entity.setGuildUuid(guildUuid.toString()); - entity.setName(name.toLowerCase(Locale.ROOT)); - } - setWarpLocation(entity, location); - warps.createOrUpdate(entity); - }); - } - - @Override - public CompletableFuture deleteWarp(UUID guildUuid, String name) - { - return runAsync(() -> - { - DeleteBuilder delete = warps.deleteBuilder(); - delete.where().eq("guild_uuid", guildUuid.toString()).and().eq("name", name.toLowerCase(Locale.ROOT)); - delete.delete(); - }); - } - - @Override - public CompletableFuture createInvite(UUID guildUuid, UUID inviterUuid, UUID inviteeUuid, Instant expiresAt) - { - return runAsync(() -> - { - deleteInviteSync(guildUuid, inviteeUuid); - GuildInviteEntity invite = new GuildInviteEntity(); - invite.setGuildUuid(guildUuid.toString()); - invite.setInviterUuid(inviterUuid.toString()); - invite.setInviteeUuid(inviteeUuid.toString()); - invite.setExpiresAt(expiresAt.toEpochMilli()); - invites.create(invite); - }); - } - - @Override - public CompletableFuture deleteInvite(UUID guildUuid, UUID inviteeUuid) - { - return runAsync(() -> deleteInviteSync(guildUuid, inviteeUuid)); - } - - @Override - public CompletableFuture> invitesFor(UUID inviteeUuid) - { - return CompletableFuture.supplyAsync(() -> - { - try - { - long now = Instant.now().toEpochMilli(); - return invites.queryForEq("invitee_uuid", inviteeUuid.toString()).stream() - .filter(invite -> invite.getExpiresAt() >= now) - .toList(); - } - catch (SQLException e) - { - throw new IllegalStateException("Failed to load guild invites", e); - } - }, executor); - } - - private Guild toGuild(GuildEntity entity) - { - String timezone = Guilds.get().api().configuration().mainConfig().getString("server.timezone", "Etc/UTC"); - Guild guild = new Guild(UUID.fromString(entity.getGuildUuid()), ZonedDateTime.ofInstant(Instant.ofEpochMilli(entity.getCreatedAt()), ZoneId.of(timezone))); - guild.setName(entity.getName()); - guild.setOwnerUuid(UUID.fromString(entity.getOwnerUuid())); - guild.setPrefix(entity.getPrefix()); - guild.setMotd(entity.getMotd()); - guild.setTagEnabled(entity.isTagEnabled()); - guild.setPublic(entity.isPublicGuild()); - guild.setHome(toLocation(entity)); - try - { - members.queryForEq("guild_uuid", entity.getGuildUuid()).forEach(member -> guild.addMember(toMember(member))); - warps.queryForEq("guild_uuid", entity.getGuildUuid()).forEach(warp -> guild.getWarps().put(warp.getName(), toLocation(warp))); - } - catch (SQLException e) - { - throw new IllegalStateException("Failed to load guild relations", e); - } - return guild; - } - - private GuildEntity toEntity(Guild guild) - { - GuildEntity entity = new GuildEntity(); - entity.setGuildUuid(guild.getGuildUuid().toString()); - entity.setName(guild.getName()); - entity.setOwnerUuid(guild.getOwnerUuid().toString()); - entity.setCreatedAt(guild.getCreatedAt().toInstant().toEpochMilli()); - entity.setPrefix(guild.getPrefix()); - entity.setMotd(guild.getMotd()); - entity.setTagEnabled(guild.isTagEnabled()); - entity.setPublicGuild(guild.isPublic()); - setHome(entity, guild.getHome()); - return entity; - } - - private Member toMember(GuildMemberEntity entity) - { - return new Member(UUID.fromString(entity.getPlayerUuid()), GuildRole.valueOf(entity.getRole())); - } - - private GuildMemberEntity memberEntity(UUID guildUuid, UUID playerUuid, GuildRole role) - { - GuildMemberEntity entity = new GuildMemberEntity(); - entity.setGuildUuid(guildUuid.toString()); - entity.setPlayerUuid(playerUuid.toString()); - entity.setRole(role.name()); - entity.setJoinedAt(Instant.now().toEpochMilli()); - return entity; - } - - private void upsertMember(UUID guildUuid, UUID playerUuid, GuildRole role) throws SQLException - { - GuildMemberEntity entity = members.queryBuilder().where() - .eq("guild_uuid", guildUuid.toString()).and().eq("player_uuid", playerUuid.toString()) - .queryForFirst(); - if (entity == null) - { - members.create(memberEntity(guildUuid, playerUuid, role)); - return; - } - entity.setRole(role.name()); - members.update(entity); - } - - private void deleteInviteSync(UUID guildUuid, UUID inviteeUuid) throws SQLException - { - DeleteBuilder delete = invites.deleteBuilder(); - delete.where().eq("guild_uuid", guildUuid.toString()).and().eq("invitee_uuid", inviteeUuid.toString()); - delete.delete(); - } - - private void deleteByGuild(Dao dao, UUID guildUuid) throws SQLException - { - DeleteBuilder delete = dao.deleteBuilder(); - delete.where().eq("guild_uuid", guildUuid.toString()); - delete.delete(); - } - - private void setHome(GuildEntity entity, CustomLocation home) - { - entity.setHomeWorld(home == null ? null : home.getWorldName()); - entity.setHomeX(home == null ? null : home.getX()); - entity.setHomeY(home == null ? null : home.getY()); - entity.setHomeZ(home == null ? null : home.getZ()); - entity.setHomeYaw(home == null ? null : home.getYaw()); - entity.setHomePitch(home == null ? null : home.getPitch()); - } - - private CustomLocation toLocation(GuildEntity entity) - { - if (entity.getHomeWorld() == null) - { - return null; - } - return new CustomLocation(entity.getHomeWorld(), entity.getHomeX(), entity.getHomeY(), entity.getHomeZ(), entity.getHomeYaw(), entity.getHomePitch()); - } - - private void setWarpLocation(GuildWarpEntity entity, CustomLocation location) - { - entity.setWorld(location.getWorldName()); - entity.setX(location.getX()); - entity.setY(location.getY()); - entity.setZ(location.getZ()); - entity.setYaw(location.getYaw()); - entity.setPitch(location.getPitch()); - } - - private CustomLocation toLocation(GuildWarpEntity entity) - { - return new CustomLocation(entity.getWorld(), entity.getX(), entity.getY(), entity.getZ(), entity.getYaw(), entity.getPitch()); - } - - private CompletableFuture runAsync(SqlRunnable runnable) - { - return CompletableFuture.runAsync(() -> - { - try - { - runnable.run(); - } - catch (SQLException e) - { - throw new IllegalStateException("Guild storage operation failed", e); - } - }, executor); - } - - @FunctionalInterface - private interface SqlRunnable - { - void run() throws SQLException; - } -} diff --git a/src/main/java/dev/plex/storage/entity/GuildEntity.java b/src/main/java/dev/plex/storage/entity/GuildEntity.java index 1628ae4..5002b8e 100644 --- a/src/main/java/dev/plex/storage/entity/GuildEntity.java +++ b/src/main/java/dev/plex/storage/entity/GuildEntity.java @@ -1,55 +1,25 @@ package dev.plex.storage.entity; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import lombok.Getter; import lombok.Setter; @Getter @Setter -@DatabaseTable public class GuildEntity { - @DatabaseField(id = true, columnName = "guild_uuid", width = 46) private String guildUuid; - - @DatabaseField(columnName = "name", width = 64, canBeNull = false) private String name; - - @DatabaseField(columnName = "prefix", width = 64) private String prefix; - - @DatabaseField(columnName = "owner_uuid", width = 46, canBeNull = false) private String ownerUuid; - - @DatabaseField(columnName = "created_at") private long createdAt; - - @DatabaseField(columnName = "home_world", width = 128) private String homeWorld; - - @DatabaseField(columnName = "home_x") private Double homeX; - - @DatabaseField(columnName = "home_y") private Double homeY; - - @DatabaseField(columnName = "home_z") private Double homeZ; - - @DatabaseField(columnName = "home_yaw") private Float homeYaw; - - @DatabaseField(columnName = "home_pitch") private Float homePitch; - - @DatabaseField(columnName = "motd", width = 3000) private String motd; - - @DatabaseField(columnName = "tag_enabled") private boolean tagEnabled = true; - - @DatabaseField(columnName = "public") private boolean publicGuild; public GuildEntity() diff --git a/src/main/java/dev/plex/storage/entity/GuildInviteEntity.java b/src/main/java/dev/plex/storage/entity/GuildInviteEntity.java index 48f85be..a93770d 100644 --- a/src/main/java/dev/plex/storage/entity/GuildInviteEntity.java +++ b/src/main/java/dev/plex/storage/entity/GuildInviteEntity.java @@ -1,28 +1,16 @@ package dev.plex.storage.entity; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import lombok.Getter; import lombok.Setter; @Getter @Setter -@DatabaseTable public class GuildInviteEntity { - @DatabaseField(generatedId = true, columnName = "id") private long id; - - @DatabaseField(columnName = "guild_uuid", width = 46, canBeNull = false) private String guildUuid; - - @DatabaseField(columnName = "inviter_uuid", width = 46, canBeNull = false) private String inviterUuid; - - @DatabaseField(columnName = "invitee_uuid", width = 46, canBeNull = false) private String inviteeUuid; - - @DatabaseField(columnName = "expires_at") private long expiresAt; public GuildInviteEntity() diff --git a/src/main/java/dev/plex/storage/entity/GuildMemberEntity.java b/src/main/java/dev/plex/storage/entity/GuildMemberEntity.java index dc4628e..0a493c9 100644 --- a/src/main/java/dev/plex/storage/entity/GuildMemberEntity.java +++ b/src/main/java/dev/plex/storage/entity/GuildMemberEntity.java @@ -1,28 +1,16 @@ package dev.plex.storage.entity; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import lombok.Getter; import lombok.Setter; @Getter @Setter -@DatabaseTable public class GuildMemberEntity { - @DatabaseField(generatedId = true, columnName = "id") private long id; - - @DatabaseField(columnName = "guild_uuid", width = 46, canBeNull = false) private String guildUuid; - - @DatabaseField(columnName = "player_uuid", width = 46, canBeNull = false) private String playerUuid; - - @DatabaseField(columnName = "role", width = 20, canBeNull = false) private String role; - - @DatabaseField(columnName = "joined_at") private long joinedAt; public GuildMemberEntity() diff --git a/src/main/java/dev/plex/storage/entity/GuildWarpEntity.java b/src/main/java/dev/plex/storage/entity/GuildWarpEntity.java index 51f9f87..95df3c6 100644 --- a/src/main/java/dev/plex/storage/entity/GuildWarpEntity.java +++ b/src/main/java/dev/plex/storage/entity/GuildWarpEntity.java @@ -1,40 +1,20 @@ package dev.plex.storage.entity; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import lombok.Getter; import lombok.Setter; @Getter @Setter -@DatabaseTable public class GuildWarpEntity { - @DatabaseField(generatedId = true, columnName = "id") private long id; - - @DatabaseField(columnName = "guild_uuid", width = 46, canBeNull = false) private String guildUuid; - - @DatabaseField(columnName = "name", width = 16, canBeNull = false) private String name; - - @DatabaseField(columnName = "world", width = 128, canBeNull = false) private String world; - - @DatabaseField(columnName = "x") private double x; - - @DatabaseField(columnName = "y") private double y; - - @DatabaseField(columnName = "z") private double z; - - @DatabaseField(columnName = "yaw") private float yaw; - - @DatabaseField(columnName = "pitch") private float pitch; public GuildWarpEntity() diff --git a/src/main/resources/db/migration/mariadb/001_initial_schema.sql b/src/main/resources/db/migration/mariadb/001_initial_schema.sql index 332b303..a57cc8b 100644 --- a/src/main/resources/db/migration/mariadb/001_initial_schema.sql +++ b/src/main/resources/db/migration/mariadb/001_initial_schema.sql @@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS {{table:guilds}} ( `home_pitch` FLOAT, `motd` VARCHAR(3000), `tag_enabled` BOOLEAN NOT NULL DEFAULT TRUE, - `public` BOOLEAN NOT NULL DEFAULT FALSE, + `is_public` BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (`guild_uuid`), UNIQUE KEY `uq_guilds_name` (`name`) ); diff --git a/src/main/resources/db/migration/postgres/001_initial_schema.sql b/src/main/resources/db/migration/postgres/001_initial_schema.sql index f8243c6..74dde81 100644 --- a/src/main/resources/db/migration/postgres/001_initial_schema.sql +++ b/src/main/resources/db/migration/postgres/001_initial_schema.sql @@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS {{table:guilds}} ( home_pitch REAL, motd VARCHAR(3000), tag_enabled BOOLEAN NOT NULL DEFAULT TRUE, - public BOOLEAN NOT NULL DEFAULT FALSE + is_public BOOLEAN NOT NULL DEFAULT FALSE ); CREATE TABLE IF NOT EXISTS {{table:members}} ( diff --git a/src/main/resources/db/migration/sqlite/001_initial_schema.sql b/src/main/resources/db/migration/sqlite/001_initial_schema.sql index f054cec..becbdb3 100644 --- a/src/main/resources/db/migration/sqlite/001_initial_schema.sql +++ b/src/main/resources/db/migration/sqlite/001_initial_schema.sql @@ -12,7 +12,7 @@ CREATE TABLE IF NOT EXISTS {{table:guilds}} ( home_pitch FLOAT, motd VARCHAR(3000), tag_enabled BOOLEAN NOT NULL DEFAULT 1, - public BOOLEAN NOT NULL DEFAULT 0 + is_public BOOLEAN NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS {{table:members}} (