From 3fa3cdfc39ba5bd7dbe960070e5e5c04896e118a Mon Sep 17 00:00:00 2001 From: Telesphoreo Date: Thu, 28 May 2026 13:15:21 -0400 Subject: [PATCH] Update Guilds --- build.gradle.kts | 3 +- src/main/java/dev/plex/Guilds.java | 34 +- .../java/dev/plex/command/GuildCommand.java | 4 + .../plex/command/sub/AcceptSubCommand.java | 165 ++++++++ .../plex/command/sub/CreateSubCommand.java | 9 +- .../dev/plex/command/sub/DenySubCommand.java | 65 +++ .../plex/command/sub/DisbandSubCommand.java | 48 +++ .../dev/plex/command/sub/InfoSubCommand.java | 37 +- .../plex/command/sub/InviteSubCommand.java | 108 +---- .../dev/plex/command/sub/LeaveSubCommand.java | 68 ++++ .../dev/plex/command/sub/OwnerSubCommand.java | 24 +- .../plex/command/sub/PrefixSubCommand.java | 27 +- .../plex/command/sub/SetHomeSubCommand.java | 27 +- .../plex/command/sub/SetWarpSubCommand.java | 21 +- .../java/dev/plex/data/SQLGuildManager.java | 165 -------- src/main/java/dev/plex/data/SQLManager.java | 46 --- src/main/java/dev/plex/guild/Guild.java | 32 +- src/main/java/dev/plex/guild/GuildHolder.java | 83 ++-- .../java/dev/plex/guild/data/GuildRole.java | 7 + src/main/java/dev/plex/guild/data/Member.java | 15 +- .../java/dev/plex/guild/data/Permission.java | 6 - src/main/java/dev/plex/guild/data/Rank.java | 5 - .../dev/plex/storage/GuildRepository.java | 41 ++ .../dev/plex/storage/OrmGuildRepository.java | 372 ++++++++++++++++++ .../dev/plex/storage/entity/GuildEntity.java | 58 +++ .../storage/entity/GuildInviteEntity.java | 31 ++ .../storage/entity/GuildMemberEntity.java | 31 ++ .../plex/storage/entity/GuildWarpEntity.java | 43 ++ .../migration/mariadb/001_initial_schema.sql | 52 +++ .../migration/postgres/001_initial_schema.sql | 47 +++ .../migration/sqlite/001_initial_schema.sql | 47 +++ src/main/resources/guilds/messages.yml | 11 +- 32 files changed, 1328 insertions(+), 404 deletions(-) create mode 100644 src/main/java/dev/plex/command/sub/AcceptSubCommand.java create mode 100644 src/main/java/dev/plex/command/sub/DenySubCommand.java create mode 100644 src/main/java/dev/plex/command/sub/DisbandSubCommand.java create mode 100644 src/main/java/dev/plex/command/sub/LeaveSubCommand.java delete mode 100644 src/main/java/dev/plex/data/SQLGuildManager.java delete mode 100644 src/main/java/dev/plex/data/SQLManager.java create mode 100644 src/main/java/dev/plex/guild/data/GuildRole.java delete mode 100644 src/main/java/dev/plex/guild/data/Permission.java delete mode 100644 src/main/java/dev/plex/guild/data/Rank.java create mode 100644 src/main/java/dev/plex/storage/GuildRepository.java create mode 100644 src/main/java/dev/plex/storage/OrmGuildRepository.java create mode 100644 src/main/java/dev/plex/storage/entity/GuildEntity.java create mode 100644 src/main/java/dev/plex/storage/entity/GuildInviteEntity.java create mode 100644 src/main/java/dev/plex/storage/entity/GuildMemberEntity.java create mode 100644 src/main/java/dev/plex/storage/entity/GuildWarpEntity.java create mode 100644 src/main/resources/db/migration/mariadb/001_initial_schema.sql create mode 100644 src/main/resources/db/migration/postgres/001_initial_schema.sql create mode 100644 src/main/resources/db/migration/sqlite/001_initial_schema.sql diff --git a/build.gradle.kts b/build.gradle.kts index cc83d20..c7b2b47 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,6 @@ dependencies { compileOnly("io.papermc.paper:paper-api:26.1.2.build.+") implementation("org.apache.commons:commons-lang3:3.20.0") compileOnly("dev.plex:api:2.0-SNAPSHOT") - compileOnly("org.json:json:20251224") implementation("org.jetbrains:annotations:26.1.0") } @@ -56,4 +55,4 @@ tasks { processResources { filteringCharset = Charsets.UTF_8.name() } -} \ No newline at end of file +} diff --git a/src/main/java/dev/plex/Guilds.java b/src/main/java/dev/plex/Guilds.java index 4ceb19d..59c5f55 100644 --- a/src/main/java/dev/plex/Guilds.java +++ b/src/main/java/dev/plex/Guilds.java @@ -2,19 +2,24 @@ package dev.plex; import dev.plex.command.GuildCommand; import dev.plex.config.ModuleConfig; -import dev.plex.data.SQLGuildManager; -import dev.plex.data.SQLManager; import dev.plex.guild.GuildHolder; +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 lombok.Getter; +import java.sql.SQLException; +import java.util.List; + @Getter public class Guilds extends PlexModule { private static Guilds module; private final GuildHolder guildHolder = new GuildHolder(); - private SQLGuildManager sqlGuildManager; + private GuildRepository guildRepository; private ModuleConfig config; @@ -31,9 +36,17 @@ public class Guilds extends PlexModule @Override public void enable() { - SQLManager.makeTables(); - sqlGuildManager = new SQLGuildManager(); - sqlGuildManager.getGuilds().whenComplete((guilds, throwable) -> + ModuleStorage storage = api().storage().forModule(this); + try + { + storage.migrations().run(List.of("001_initial_schema")); + } + catch (SQLException e) + { + throw new IllegalStateException("Failed to run Guilds migrations", e); + } + guildRepository = new OrmGuildRepository(storage); + guildRepository.loadGuilds().whenComplete((guilds, throwable) -> { if (throwable != null) { @@ -46,18 +59,15 @@ public class Guilds extends PlexModule return; } api().logging().debug("Finished loading {0} guilds", guilds.size()); - guilds.forEach(guildHolder::addGuild); + guildHolder.replaceAll(guilds); }); + registerListener(new ChatHandlerImpl()); } @Override public void disable() { - // Unregistering listeners / commands is handled by Plex - if (sqlGuildManager != null) - { - this.getGuildHolder().getGuilds().forEach(sqlGuildManager::updateGuild); - } + guildHolder.clear(); } public static Guilds get() diff --git a/src/main/java/dev/plex/command/GuildCommand.java b/src/main/java/dev/plex/command/GuildCommand.java index 837fabf..7df5228 100644 --- a/src/main/java/dev/plex/command/GuildCommand.java +++ b/src/main/java/dev/plex/command/GuildCommand.java @@ -30,6 +30,8 @@ public class GuildCommand extends SimplePlexCommand .permission("plex.guilds.guild") .build()); this.registerSubCommand(new CreateSubCommand()); + this.registerSubCommand(new DisbandSubCommand()); + this.registerSubCommand(new LeaveSubCommand()); this.registerSubCommand(new InfoSubCommand()); this.registerSubCommand(new PrefixSubCommand()); this.registerSubCommand(new SetWarpSubCommand()); @@ -40,6 +42,8 @@ public class GuildCommand extends SimplePlexCommand this.registerSubCommand(new HomeSubCommand()); this.registerSubCommand(new OwnerSubCommand()); this.registerSubCommand(new InviteSubCommand()); + this.registerSubCommand(new AcceptSubCommand()); + this.registerSubCommand(new DenySubCommand()); } @Override diff --git a/src/main/java/dev/plex/command/sub/AcceptSubCommand.java b/src/main/java/dev/plex/command/sub/AcceptSubCommand.java new file mode 100644 index 0000000..fb8aa17 --- /dev/null +++ b/src/main/java/dev/plex/command/sub/AcceptSubCommand.java @@ -0,0 +1,165 @@ +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; +import dev.plex.guild.data.Member; +import dev.plex.storage.entity.GuildInviteEntity; +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; + +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +public class AcceptSubCommand extends SimplePlexCommand +{ + public AcceptSubCommand() + { + super(command("accept") + .description("Accepts a guild invite") + .usage("/guild ") + .permission("plex.guilds.accept") + .source(RequiredCommandSource.IN_GAME) + .build()); + } + + @Override + protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args) + { + if (args.length == 0) + { + return usage(); + } + assert player != null; + Guilds.get().getGuildRepository().invitesFor(player.getUniqueId()).whenComplete((invites, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + GuildInviteEntity invite = findInvite(invites, String.join(" ", args)); + if (invite == null) + { + send(player, messageComponent("guildNotValidInvite")); + return; + } + if (invite.getExpiresAt() < Instant.now().toEpochMilli()) + { + Guilds.get().getGuildRepository().deleteInvite(UUID.fromString(invite.getGuildUuid()), player.getUniqueId()); + send(player, messageComponent("guildInviteExpired")); + return; + } + Guild target = Guilds.get().getGuildHolder().guildById(UUID.fromString(invite.getGuildUuid())).orElse(null); + if (target == null) + { + send(player, messageComponent("guildNotValidInvite")); + return; + } + leaveCurrentIfNeeded(player, target, invite); + }); + return null; + } + + private GuildInviteEntity findInvite(List invites, String guildName) + { + return invites.stream() + .filter(invite -> Guilds.get().getGuildHolder().guildById(UUID.fromString(invite.getGuildUuid())) + .map(guild -> guild.getName().equalsIgnoreCase(guildName)) + .orElse(false)) + .findFirst() + .orElse(null); + } + + private void leaveCurrentIfNeeded(Player player, Guild target, GuildInviteEntity invite) + { + Guilds.get().getGuildHolder().guild(player.getUniqueId()).ifPresentOrElse(current -> + { + if (current.getGuildUuid().equals(target.getGuildUuid())) + { + send(player, messageComponent("guildInThis")); + return; + } + if (current.isOwner(player.getUniqueId()) && current.getMembers().size() > 1) + { + send(player, messageComponent("guildOwnerLeaveBlocked")); + return; + } + if (current.isOwner(player.getUniqueId())) + { + Guilds.get().getGuildRepository().deleteGuild(current.getGuildUuid()).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + Guilds.get().getGuildHolder().removeGuild(current.getGuildUuid()); + joinTarget(player, target, invite); + }); + return; + } + Guilds.get().getGuildRepository().removeMember(current.getGuildUuid(), player.getUniqueId()).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + current.getMembers().removeIf(member -> member.getUuid().equals(player.getUniqueId())); + Guilds.get().getGuildHolder().unindexMember(player.getUniqueId()); + current.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(memberPlayer -> + send(memberPlayer, messageComponent("guildMemberLeft", player.getName()))); + joinTarget(player, target, invite); + }); + }, () -> joinTarget(player, target, invite)); + } + + private void joinTarget(Player player, Guild guild, GuildInviteEntity invite) + { + UUID guildUuid = guild.getGuildUuid(); + Guilds.get().getGuildRepository().addMember(guildUuid, player.getUniqueId(), GuildRole.MEMBER) + .thenCompose(unused -> Guilds.get().getGuildRepository().deleteInvite(guildUuid, player.getUniqueId())) + .whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + guild.addMember(player.getUniqueId()); + Guilds.get().getGuildHolder().indexMember(guildUuid, player.getUniqueId()); + guild.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(memberPlayer -> + send(memberPlayer, messageComponent("guildMemberJoined", player.getName()))); + }); + } + + @Override + protected @NotNull List suggestions(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException + { + if (!(sender instanceof Player player) || args.length != 1) + { + return ImmutableList.of(); + } + try + { + return Guilds.get().getGuildRepository().invitesFor(player.getUniqueId()).join().stream() + .map(invite -> Guilds.get().getGuildHolder().guildById(UUID.fromString(invite.getGuildUuid())).orElse(null)) + .filter(Objects::nonNull) + .map(Guild::getName) + .toList(); + } + catch (RuntimeException ignored) + { + return ImmutableList.of(); + } + } +} diff --git a/src/main/java/dev/plex/command/sub/CreateSubCommand.java b/src/main/java/dev/plex/command/sub/CreateSubCommand.java index 0a32e8a..a27c516 100644 --- a/src/main/java/dev/plex/command/sub/CreateSubCommand.java +++ b/src/main/java/dev/plex/command/sub/CreateSubCommand.java @@ -38,10 +38,15 @@ public class CreateSubCommand extends SimplePlexCommand { return messageComponent("alreadyInGuild"); } - Guilds.get().getSqlGuildManager().insertGuild(Guild.create(player, StringUtils.join(args, " "))).whenComplete((guild, throwable) -> + Guilds.get().getGuildRepository().createGuild(player, StringUtils.join(args, " ")).whenComplete((guild, throwable) -> { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } Guilds.get().getGuildHolder().addGuild(guild); - send(player, mmString("Created guild named " + guild.getName())); + send(player, messageComponent("guildCreated", guild.getName())); }); return null; } diff --git a/src/main/java/dev/plex/command/sub/DenySubCommand.java b/src/main/java/dev/plex/command/sub/DenySubCommand.java new file mode 100644 index 0000000..24fd4b1 --- /dev/null +++ b/src/main/java/dev/plex/command/sub/DenySubCommand.java @@ -0,0 +1,65 @@ +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; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public class DenySubCommand extends SimplePlexCommand +{ + public DenySubCommand() + { + super(command("deny") + .description("Denies a guild invite") + .usage("/guild ") + .permission("plex.guilds.deny") + .source(RequiredCommandSource.IN_GAME) + .build()); + } + + @Override + protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args) + { + if (args.length == 0) + { + return usage(); + } + assert player != null; + Guilds.get().getGuildRepository().invitesFor(player.getUniqueId()).whenComplete((invites, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + GuildInviteEntity invite = invites.stream() + .filter(candidate -> Guilds.get().getGuildHolder().guildById(UUID.fromString(candidate.getGuildUuid())) + .map(guild -> guild.getName().equalsIgnoreCase(String.join(" ", args))) + .orElse(false)) + .findFirst() + .orElse(null); + if (invite == null) + { + send(player, messageComponent("guildNotValidInvite")); + return; + } + Guilds.get().getGuildRepository().deleteInvite(UUID.fromString(invite.getGuildUuid()), player.getUniqueId()).whenComplete((unused, deleteThrowable) -> + { + if (deleteThrowable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + send(player, messageComponent("guildInviteDenied")); + }); + }); + return null; + } +} diff --git a/src/main/java/dev/plex/command/sub/DisbandSubCommand.java b/src/main/java/dev/plex/command/sub/DisbandSubCommand.java new file mode 100644 index 0000000..0eb8f86 --- /dev/null +++ b/src/main/java/dev/plex/command/sub/DisbandSubCommand.java @@ -0,0 +1,48 @@ +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; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class DisbandSubCommand extends SimplePlexCommand +{ + public DisbandSubCommand() + { + super(command("disband") + .description("Disbands your guild") + .usage("/guild ") + .permission("plex.guilds.disband") + .source(RequiredCommandSource.IN_GAME) + .build()); + } + + @Override + protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args) + { + assert player != null; + Guilds.get().getGuildHolder().guild(player.getUniqueId()).ifPresentOrElse(guild -> + { + if (!guild.isOwner(player.getUniqueId())) + { + send(player, messageComponent("guildNotOwner")); + return; + } + Guilds.get().getGuildRepository().deleteGuild(guild.getGuildUuid()).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + Guilds.get().getGuildHolder().removeGuild(guild.getGuildUuid()); + send(player, messageComponent("guildDisbanded")); + }); + }, () -> send(player, messageComponent("guildNotFound"))); + return null; + } +} diff --git a/src/main/java/dev/plex/command/sub/InfoSubCommand.java b/src/main/java/dev/plex/command/sub/InfoSubCommand.java index 6d9ee2c..78538c8 100644 --- a/src/main/java/dev/plex/command/sub/InfoSubCommand.java +++ b/src/main/java/dev/plex/command/sub/InfoSubCommand.java @@ -4,6 +4,7 @@ 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; import java.util.Collections; import java.util.List; @@ -31,29 +32,51 @@ public class InfoSubCommand extends SimplePlexCommand private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss a"); @Override - protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] strings) + protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args) { assert player != null; CompletableFuture.runAsync(() -> { - Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild -> + Guild guild = resolveGuild(player, args); + if (guild == null) + { + send(player, messageComponent("guildNotFound")); + return; + } { send(player, mmString("====" + guild.getName() + "====")); send(player, mmString("")); - send(player, mmString("Owner: " + playerName(guild.getOwner().getUuid()))); - List members = guild.getMembers().stream().filter(member -> !member.getUuid().equals(guild.getOwner().getUuid())).map(member -> playerName(member.getUuid())).toList(); + send(player, mmString("Owner: " + playerName(guild.getOwnerUuid()))); + List members = guild.getMembers().stream().filter(member -> !member.getUuid().equals(guild.getOwnerUuid())).map(member -> playerName(member.getUuid())).toList(); send(player, mmString("Members (" + members.size() + "): " + StringUtils.join(members, ", "))); - send(player, mmString("Moderators (" + guild.getModerators().size() + "): " + StringUtils.join(guild.getModerators().stream().map(this::playerName).toList(), ", "))); send(player, mmString("Prefix: " + (guild.getPrefix() == null ? "N/A" : guild.getPrefix()))); send(player, mmString("Created At: " + formatter.format(guild.getCreatedAt()))); - }, () -> send(player, messageComponent("guildNotFound"))); + } }, Guilds.get().api().scheduler().asyncExecutor()); return null; } + private Guild resolveGuild(Player sender, String[] args) + { + if (args.length == 0) + { + return Guilds.get().getGuildHolder().guild(sender.getUniqueId()).orElse(null); + } + PlexPlayerView player = api().players().byName(args[0]).orElse(null); + if (player != null) + { + Guild guild = Guilds.get().getGuildHolder().guild(player.uuid()).orElse(null); + if (guild != null) + { + return guild; + } + } + return Guilds.get().getGuildHolder().guildByName(StringUtils.join(args, " ")).orElse(null); + } + private String playerName(java.util.UUID uuid) { - return api().players().byUuid(uuid).map(PlexPlayerView::name).orElse("Unable to load cache..."); + return api().players().player(uuid).map(PlexPlayerView::name).orElse(uuid.toString()); } @Override diff --git a/src/main/java/dev/plex/command/sub/InviteSubCommand.java b/src/main/java/dev/plex/command/sub/InviteSubCommand.java index 3ea4443..6a2ae44 100644 --- a/src/main/java/dev/plex/command/sub/InviteSubCommand.java +++ b/src/main/java/dev/plex/command/sub/InviteSubCommand.java @@ -4,23 +4,15 @@ 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.GuildHolder; -import dev.plex.guild.data.Member; import net.kyori.adventure.text.Component; -import org.apache.commons.lang3.StringUtils; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; -// TODO: 5/9/2022 5 minute timeout for invites -// TODO: 5/9/2022 deny command maybe? -// TODO: 5/9/2022 deny members from inviting themselves or existing members in the current guild public class InviteSubCommand extends SimplePlexCommand { @@ -43,75 +35,34 @@ public class InviteSubCommand extends SimplePlexCommand return usage(); } assert player != null; - if (args[0].equalsIgnoreCase("accept")) - { - if (!GuildHolder.PENDING_INVITES.containsKey(player.getUniqueId())) - { - return messageComponent("guildNoInvite"); - } - String guildName = StringUtils.join(args, " ", 1, args.length); - GuildHolder.PENDING_INVITES.get(player.getUniqueId()).stream().filter(guild -> guild.getName().equalsIgnoreCase(guildName)).findFirst().ifPresentOrElse(guild -> - { - AtomicBoolean continueCheck = new AtomicBoolean(true); - Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresent(guild1 -> - { - if (guild1.getGuildUuid().equals(guild.getGuildUuid())) - { - send(player, messageComponent("guildInThis")); - continueCheck.set(false); - return; - } - if (guild1.getOwner().getUuid().equals(player.getUniqueId())) - { - if (guild1.getMembers().size() - 1 > 0) - { - send(player, messageComponent("guildDisbandNeeded")); - continueCheck.set(false); - return; - } - else - { - Guilds.get().getSqlGuildManager().deleteGuild(guild1.getGuildUuid()).whenComplete((unused, throwable) -> - { - send(player, messageComponent("guildAutoDisbanded")); - }); - } - } - guild1.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(player1 -> - { - send(player1, messageComponent("guildMemberLeft", player.getName())); - }); - guild1.getMembers().removeIf(member -> member.getUuid().equals(player.getUniqueId())); - }); - if (!continueCheck.get()) - { - return; - } - GuildHolder.PENDING_INVITES.remove(player.getUniqueId()); - guild.addMember(player.getUniqueId()); - guild.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(player1 -> - { - send(player1, messageComponent("guildMemberJoined", player.getName())); - }); - }, () -> send(player, messageComponent("guildNotValidInvite"))); - return null; - } Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild -> { - if (!guild.getOwner().getUuid().equals(player.getUniqueId())) + if (!guild.isOwner(player.getUniqueId())) { send(player, messageComponent("guildNotOwner")); return; } Player target = getNonNullPlayer(args[0]); - boolean invite = GuildHolder.sendInvite(target.getUniqueId(), guild); - if (!invite) + if (target.getUniqueId().equals(player.getUniqueId())) { - send(player, messageComponent("guildInviteExists")); + send(player, messageComponent("guildCannotInviteSelf")); return; } - send(player, messageComponent("guildInviteSent", target.getName())); - send(target, messageComponent("guildInviteReceived", player.getName(), guild.getName())); + if (guild.getMember(target.getUniqueId()) != null || Guilds.get().getGuildHolder().guild(target.getUniqueId()).isPresent()) + { + send(player, messageComponent("guildTargetAlreadyInGuild")); + return; + } + Guilds.get().getGuildRepository().createInvite(guild.getGuildUuid(), player.getUniqueId(), target.getUniqueId(), Instant.now().plus(5, ChronoUnit.MINUTES)).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + send(player, messageComponent("guildInviteSent", target.getName())); + send(target, messageComponent("guildInviteReceived", player.getName(), guild.getName())); + }); }, () -> send(player, messageComponent("guildNotFound"))); return null; } @@ -119,23 +70,6 @@ public class InviteSubCommand extends SimplePlexCommand @Override protected @NotNull List suggestions(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { - if (!(sender instanceof Player player)) - { - return ImmutableList.of(); - } - if (args.length == 0) - { - return ImmutableList.of(); - } - if (args[0].equalsIgnoreCase("accept") && args.length == 2) - { - if (!GuildHolder.PENDING_INVITES.containsKey(player.getUniqueId())) - { - return ImmutableList.of(); - } - api().logging().debug("Completing pending guild invites"); - return GuildHolder.PENDING_INVITES.get(player.getUniqueId()).stream().map(Guild::getName).collect(Collectors.toList()); - } - return ImmutableList.of(); + return args.length == 1 ? onlinePlayerNames() : ImmutableList.of(); } } diff --git a/src/main/java/dev/plex/command/sub/LeaveSubCommand.java b/src/main/java/dev/plex/command/sub/LeaveSubCommand.java new file mode 100644 index 0000000..fb54ce6 --- /dev/null +++ b/src/main/java/dev/plex/command/sub/LeaveSubCommand.java @@ -0,0 +1,68 @@ +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; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class LeaveSubCommand extends SimplePlexCommand +{ + public LeaveSubCommand() + { + super(command("leave") + .description("Leaves your guild") + .usage("/guild ") + .permission("plex.guilds.leave") + .source(RequiredCommandSource.IN_GAME) + .build()); + } + + @Override + protected Component execute(@NotNull CommandSender commandSender, @Nullable Player player, @NotNull String[] args) + { + assert player != null; + Guilds.get().getGuildHolder().guild(player.getUniqueId()).ifPresentOrElse(guild -> + { + if (guild.isOwner(player.getUniqueId())) + { + if (guild.getMembers().size() > 1) + { + send(player, messageComponent("guildOwnerLeaveBlocked")); + return; + } + Guilds.get().getGuildRepository().deleteGuild(guild.getGuildUuid()).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + Guilds.get().getGuildHolder().removeGuild(guild.getGuildUuid()); + send(player, messageComponent("guildAutoDisbanded")); + }); + return; + } + Guilds.get().getGuildRepository().removeMember(guild.getGuildUuid(), player.getUniqueId()).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + guild.getMembers().removeIf(member -> member.getUuid().equals(player.getUniqueId())); + Guilds.get().getGuildHolder().unindexMember(player.getUniqueId()); + guild.getMembers().stream().map(Member::getPlayer).filter(Objects::nonNull).forEach(memberPlayer -> + send(memberPlayer, messageComponent("guildMemberLeft", player.getName()))); + send(player, messageComponent("guildLeft")); + }); + }, () -> send(player, messageComponent("guildNotFound"))); + return null; + } +} diff --git a/src/main/java/dev/plex/command/sub/OwnerSubCommand.java b/src/main/java/dev/plex/command/sub/OwnerSubCommand.java index 542f4b3..d9f904d 100644 --- a/src/main/java/dev/plex/command/sub/OwnerSubCommand.java +++ b/src/main/java/dev/plex/command/sub/OwnerSubCommand.java @@ -4,6 +4,7 @@ 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; import java.util.Collections; import java.util.List; @@ -20,7 +21,7 @@ public class OwnerSubCommand extends SimplePlexCommand super(command("owner") .description("Sets the guild owner") .usage("/guild ") - .aliases("setowner") + .aliases("setowner,promote") .permission("plex.guilds.owner") .source(RequiredCommandSource.IN_GAME) .build()); @@ -36,7 +37,7 @@ public class OwnerSubCommand extends SimplePlexCommand assert player != null; Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild -> { - if (!guild.getOwner().getUuid().equals(player.getUniqueId())) + if (!guild.isOwner(player.getUniqueId())) { send(player, messageComponent("guildNotOwner")); return; @@ -54,10 +55,21 @@ public class OwnerSubCommand extends SimplePlexCommand send(player, messageComponent("guildMemberNotFound")); return; } - guild.setOwner(member); - guild.getMembers().remove(member); - guild.getMembers().add(memberSender); - send(player, messageComponent("guildOwnerSet", plexPlayer.name())); + Guilds.get().getGuildRepository().transferOwner(guild.getGuildUuid(), member.getUuid(), player.getUniqueId()).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + guild.setOwnerUuid(member.getUuid()); + member.setRole(GuildRole.OWNER); + if (memberSender != null) + { + memberSender.setRole(GuildRole.MEMBER); + } + send(player, messageComponent("guildOwnerSet", plexPlayer.name())); + }); }, () -> send(player, messageComponent("guildNotFound"))); return null; } diff --git a/src/main/java/dev/plex/command/sub/PrefixSubCommand.java b/src/main/java/dev/plex/command/sub/PrefixSubCommand.java index f80543e..4e0866d 100644 --- a/src/main/java/dev/plex/command/sub/PrefixSubCommand.java +++ b/src/main/java/dev/plex/command/sub/PrefixSubCommand.java @@ -36,19 +36,36 @@ public class PrefixSubCommand extends SimplePlexCommand assert player != null; Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild -> { - if (!guild.getOwner().getUuid().equals(player.getUniqueId())) + if (!guild.isOwner(player.getUniqueId())) { send(player, messageComponent("guildNotOwner")); return; } if (args[0].equalsIgnoreCase("clear") || args[0].equalsIgnoreCase("off")) { - guild.setPrefix(null); - send(player, messageComponent("guildPrefixCleared")); + Guilds.get().getGuildRepository().updatePrefix(guild.getGuildUuid(), null).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + guild.setPrefix(null); + send(player, messageComponent("guildPrefixCleared")); + }); return; } - guild.setPrefix(StringUtils.join(args, " ")); - send(player, messageComponent("guildPrefixSet", GuildUtil.miniMessageWithoutEvents(guild.getPrefix()))); + String prefix = StringUtils.join(args, " "); + Guilds.get().getGuildRepository().updatePrefix(guild.getGuildUuid(), prefix).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + guild.setPrefix(prefix); + send(player, messageComponent("guildPrefixSet", GuildUtil.miniMessageWithoutEvents(guild.getPrefix()))); + }); }, () -> send(player, messageComponent("guildNotFound"))); return null; } diff --git a/src/main/java/dev/plex/command/sub/SetHomeSubCommand.java b/src/main/java/dev/plex/command/sub/SetHomeSubCommand.java index 0aa4b63..1a63df8 100644 --- a/src/main/java/dev/plex/command/sub/SetHomeSubCommand.java +++ b/src/main/java/dev/plex/command/sub/SetHomeSubCommand.java @@ -31,7 +31,7 @@ public class SetHomeSubCommand extends SimplePlexCommand assert player != null; Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild -> { - if (!guild.getOwner().getUuid().equals(player.getUniqueId())) + if (!guild.isOwner(player.getUniqueId())) { send(player, messageComponent("guildNotOwner")); return; @@ -43,12 +43,29 @@ public class SetHomeSubCommand extends SimplePlexCommand send(player, messageComponent("guildHomeNotFound")); return; } - guild.setHome(null); - send(player, messageComponent("guildHomeRemoved")); + Guilds.get().getGuildRepository().updateHome(guild.getGuildUuid(), null).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + guild.setHome(null); + send(player, messageComponent("guildHomeRemoved")); + }); return; } - guild.setHome(CustomLocation.fromLocation(player.getLocation())); - send(player, messageComponent("guildHomeSet")); + CustomLocation home = CustomLocation.fromLocation(player.getLocation()); + Guilds.get().getGuildRepository().updateHome(guild.getGuildUuid(), home).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + guild.setHome(home); + send(player, messageComponent("guildHomeSet")); + }); }, () -> send(player, messageComponent("guildNotFound"))); return null; } diff --git a/src/main/java/dev/plex/command/sub/SetWarpSubCommand.java b/src/main/java/dev/plex/command/sub/SetWarpSubCommand.java index 9ba4498..1f4d37a 100644 --- a/src/main/java/dev/plex/command/sub/SetWarpSubCommand.java +++ b/src/main/java/dev/plex/command/sub/SetWarpSubCommand.java @@ -37,7 +37,7 @@ public class SetWarpSubCommand extends SimplePlexCommand assert player != null; Guilds.get().getGuildHolder().getGuild(player.getUniqueId()).ifPresentOrElse(guild -> { - if (!guild.getOwner().getUuid().equals(player.getUniqueId())) + if (!guild.isOwner(player.getUniqueId())) { send(player, messageComponent("guildNotOwner")); return; @@ -48,18 +48,23 @@ public class SetWarpSubCommand extends SimplePlexCommand send(player, mmString("The max length of a warp name is 16 characters!")); return; } - if (guild.getWarps().containsKey(warpName.toLowerCase())) - { - send(player, messageComponent("guildWarpExists", warpName)); - return; - } if (!StringUtils.isAlphanumericSpace(warpName.toLowerCase(Locale.ROOT))) { send(player, messageComponent("guildWarpAlphanumeric")); return; } - guild.getWarps().put(warpName.toLowerCase(), CustomLocation.fromLocation(player.getLocation())); - send(player, messageComponent("guildWarpCreated", warpName)); + CustomLocation location = CustomLocation.fromLocation(player.getLocation()); + String localName = warpName.toLowerCase(Locale.ROOT); + Guilds.get().getGuildRepository().upsertWarp(guild.getGuildUuid(), localName, location).whenComplete((unused, throwable) -> + { + if (throwable != null) + { + send(player, messageComponent("guildStorageFailed")); + return; + } + guild.getWarps().put(localName, location); + send(player, messageComponent("guildWarpCreated", warpName)); + }); }, () -> send(player, messageComponent("guildNotFound"))); return null; } diff --git a/src/main/java/dev/plex/data/SQLGuildManager.java b/src/main/java/dev/plex/data/SQLGuildManager.java deleted file mode 100644 index bc56e41..0000000 --- a/src/main/java/dev/plex/data/SQLGuildManager.java +++ /dev/null @@ -1,165 +0,0 @@ -package dev.plex.data; - -import com.google.common.collect.Lists; -import com.google.common.reflect.TypeToken; -import dev.plex.Guilds; -import com.google.gson.Gson; -import dev.plex.guild.Guild; -import dev.plex.guild.data.Member; -import dev.plex.util.CustomLocation; -import dev.plex.util.GuildUtil; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -public class SQLGuildManager -{ - private static final Gson GSON = new Gson(); - - private static final String SELECT_GUILD = "SELECT * FROM `guilds`"; - private static final String INSERT_GUILD = "INSERT INTO `guilds` (`guildUuid`, `name`, `owner`, `createdAt`, `members`, `moderators`, `prefix`, `motd`, `ranks`, `defaultRank`, `warps`, `home`, `tagEnabled`, `isPublic`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - private static final String DELETE_GUILD = "DELETE FROM `guilds` WHERE guildUuid=?"; - private static final String UPDATE_GUILD = "UPDATE `guilds` SET name=?, owner=?, members=?, moderators=?, prefix=?, motd=?, ranks=?, defaultRank=?, home=?, warps=?, tagEnabled=?, isPublic=? WHERE guildUuid=?"; - - public CompletableFuture insertGuild(Guild guild) - { - return supplyStorageAsync(connection -> - { - try (PreparedStatement statement = connection.prepareStatement(INSERT_GUILD)) - { - statement.setString(1, guild.getGuildUuid().toString()); - statement.setString(2, guild.getName()); - statement.setString(3, GSON.toJson(guild.getOwner())); - statement.setLong(4, guild.getCreatedAt().toInstant().toEpochMilli()); - statement.setString(5, GSON.toJson(guild.getMembers())); - statement.setString(6, GSON.toJson(guild.getModerators().stream().map(UUID::toString).collect(Collectors.toList()))); - statement.setString(7, guild.getPrefix()); - statement.setString(8, guild.getMotd()); - statement.setString(9, GSON.toJson(guild.getRanks())); - statement.setString(10, GSON.toJson(guild.getDefaultRank())); - statement.setString(11, GSON.toJson(guild.getWarps())); - statement.setString(12, GSON.toJson(guild.getHome())); - statement.setBoolean(13, guild.isTagEnabled()); - statement.setBoolean(14, guild.isPublic()); - statement.execute(); - return guild; - } - }); - } - - public CompletableFuture deleteGuild(UUID uuid) - { - return supplyStorageAsync(connection -> - { - try (PreparedStatement statement = connection.prepareStatement(DELETE_GUILD)) - { - statement.setString(1, uuid.toString()); - statement.execute(); - return null; - } - }); - } - - public CompletableFuture updateGuild(Guild guild) - { - return supplyStorageAsync(connection -> - { - try (PreparedStatement statement = connection.prepareStatement(UPDATE_GUILD)) - { - statement.setString(1, guild.getName()); - statement.setString(2, GSON.toJson(guild.getOwner())); - statement.setString(3, GSON.toJson(guild.getMembers())); - statement.setString(4, GSON.toJson(guild.getModerators().stream().map(UUID::toString).collect(Collectors.toList()))); - statement.setString(5, guild.getPrefix()); - statement.setString(6, guild.getMotd()); - statement.setString(7, GSON.toJson(guild.getRanks())); - statement.setString(8, GSON.toJson(guild.getDefaultRank())); - statement.setString(9, GSON.toJson(guild.getHome())); - statement.setString(10, GSON.toJson(guild.getWarps())); - statement.setBoolean(11, guild.isTagEnabled()); - statement.setBoolean(12, guild.isPublic()); - statement.setString(13, guild.getGuildUuid().toString()); - statement.executeUpdate(); - return guild; - } - }); - } - - private List getGuildsSync(Connection connection) throws SQLException - { - List guilds = Lists.newArrayList(); - try (PreparedStatement statement = connection.prepareStatement(SELECT_GUILD); - ResultSet set = statement.executeQuery()) - { - while (set.next()) - { - String timezone = Guilds.get().api().configuration().mainConfig().getString("server.timezone", "Etc/UTC"); - Guild guild = new Guild(UUID.fromString(set.getString("guildUuid")), - ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("createdAt")), ZoneId.of(timezone).getRules().getOffset(Instant.now()))); - guild.setName(set.getString("name")); - guild.setOwner(GSON.fromJson(set.getString("owner"), Member.class)); - List members = new Gson().fromJson(set.getString("members"), new TypeToken>() - { - }.getType()); - if (members != null) - { - members.forEach(guild::addMember); - } - List moderators = new Gson().fromJson(set.getString("moderators"), new TypeToken>() - { - }.getType()); - if (moderators != null) - { - moderators.stream().map(UUID::fromString).forEach(guild.getModerators()::add); - } - guild.setPrefix(set.getString("prefix")); - guild.setMotd(set.getString("motd")); - guild.setHome(GSON.fromJson(set.getString("home"), CustomLocation.class)); - guild.setTagEnabled(set.getBoolean("tagEnabled")); - Map warps = GSON.fromJson(set.getString("warps"), new TypeToken>() - { - }.getType()); - if (warps != null) - { - Guilds.get().api().logging().debug("Loaded {0} warps for {1} guild", warps.size(), guild.getName()); - guild.getWarps().putAll(warps); - } - guild.setPublic(set.getBoolean("isPublic")); - guilds.add(guild); - } - } - return guilds; - } - - public CompletableFuture> getGuilds() - { - return supplyStorageAsync(this::getGuildsSync); - } - - private CompletableFuture supplyStorageAsync(dev.plex.api.storage.StorageApi.SqlFunction function) - { - return CompletableFuture.supplyAsync(() -> - { - try - { - return Guilds.get().api().storage().withConnection(function); - } - catch (SQLException e) - { - GuildUtil.throwExceptionSync(e); - return null; - } - }, Guilds.get().api().scheduler().asyncExecutor()); - } - -} diff --git a/src/main/java/dev/plex/data/SQLManager.java b/src/main/java/dev/plex/data/SQLManager.java deleted file mode 100644 index 447879a..0000000 --- a/src/main/java/dev/plex/data/SQLManager.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.plex.data; - -import dev.plex.Guilds; -import dev.plex.util.GuildUtil; - -import java.sql.PreparedStatement; -import java.sql.SQLException; - -public class SQLManager -{ - public static void makeTables() - { - try - { - Guilds.get().api().storage().withConnection(connection -> - { - try (PreparedStatement statement = connection.prepareStatement( - "CREATE TABLE IF NOT EXISTS `guilds` (" + - "`guildUuid` VARCHAR(46) NOT NULL, " + - "`name` VARCHAR(2000) NOT NULL, " + - "`owner` LONGTEXT NOT NULL, " + - "`createdAt` BIGINT NOT NULL, " + - "`prefix` VARCHAR(2000), " + - "`motd` VARCHAR(3000), " + - "`home` VARCHAR(1000)," + - "`members` LONGTEXT, " + - "`moderators` LONGTEXT, " + - "`ranks` LONGTEXT, " + - "`defaultRank` LONGTEXT, " + - "`warps` LONGTEXT, " + - "`tagEnabled` BOOLEAN, " + - "`isPublic` BOOLEAN, " + - "PRIMARY KEY (`guildUuid`)" + - ");")) - { - statement.execute(); - } - return null; - }); - } - catch (SQLException e) - { - GuildUtil.throwExceptionSync(e); - } - } -} diff --git a/src/main/java/dev/plex/guild/Guild.java b/src/main/java/dev/plex/guild/Guild.java index ade567c..3f7e398 100644 --- a/src/main/java/dev/plex/guild/Guild.java +++ b/src/main/java/dev/plex/guild/Guild.java @@ -3,8 +3,8 @@ package dev.plex.guild; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import dev.plex.Guilds; +import dev.plex.guild.data.GuildRole; import dev.plex.guild.data.Member; -import dev.plex.guild.data.Rank; import dev.plex.util.CustomLocation; import dev.plex.util.GuildUtil; import lombok.Data; @@ -23,51 +23,43 @@ public class Guild private final UUID guildUuid; private final ZonedDateTime createdAt; private final List members = Lists.newArrayList(); - private final List moderators = Lists.newArrayList(); - private final List ranks = Lists.newArrayList(); private final Map warps = Maps.newHashMap(); private String name; - private Member owner; + private UUID ownerUuid; private String prefix; private String motd; private CustomLocation home; - private boolean tagEnabled; - private Rank defaultRank = new Rank("default", null); - private boolean isPublic = false; - + private boolean tagEnabled = true; + private boolean isPublic; public static Guild create(Player player, String guildName) { String timezone = Guilds.get().api().configuration().mainConfig().getString("server.timezone", "Etc/UTC"); Guild guild = new Guild(UUID.randomUUID(), ZonedDateTime.now(ZoneId.of(timezone))); guild.setName(PlainTextComponentSerializer.plainText().serialize(GuildUtil.miniMessageWithoutEvents(guildName))); - guild.setOwner(new Member(player.getUniqueId())); + guild.setOwnerUuid(player.getUniqueId()); + guild.addMember(new Member(player.getUniqueId(), GuildRole.OWNER)); return guild; } public Member getMember(UUID uuid) { - if (owner.getUuid().equals(uuid)) - { - return owner; - } - return members.stream().filter(m -> m.getUuid().equals(uuid)).findFirst().orElse(null); + return members.stream().filter(member -> member.getUuid().equals(uuid)).findFirst().orElse(null); } public void addMember(UUID uuid) { - addMember(new Member(uuid)); + addMember(new Member(uuid, GuildRole.MEMBER)); } public void addMember(Member member) { - this.members.add(member); + members.removeIf(existing -> existing.getUuid().equals(member.getUuid())); + members.add(member); } - public List getMembers() + public boolean isOwner(UUID uuid) { - List allMembers = Lists.newArrayList(members); - allMembers.add(owner); - return allMembers; + return ownerUuid != null && ownerUuid.equals(uuid); } } diff --git a/src/main/java/dev/plex/guild/GuildHolder.java b/src/main/java/dev/plex/guild/GuildHolder.java index 094b7fe..c954bf0 100644 --- a/src/main/java/dev/plex/guild/GuildHolder.java +++ b/src/main/java/dev/plex/guild/GuildHolder.java @@ -1,51 +1,84 @@ package dev.plex.guild; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import dev.plex.guild.data.Member; -import java.util.*; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; public class GuildHolder { - public static final Map> PENDING_INVITES = Maps.newHashMap(); - private static final List GUILDS = Lists.newArrayList(); + private final Map guildsById = new ConcurrentHashMap<>(); + private final Map guildByPlayer = new ConcurrentHashMap<>(); - public static boolean sendInvite(UUID uuid, Guild guild) + public void replaceAll(Collection guilds) { - if (PENDING_INVITES.containsKey(uuid) && PENDING_INVITES.get(uuid).stream().anyMatch(guild1 -> guild1.getGuildUuid().equals(guild.getGuildUuid()))) - { - return false; - } - if (PENDING_INVITES.containsKey(uuid)) - { - PENDING_INVITES.get(uuid).add(guild); - } - else - { - PENDING_INVITES.put(uuid, Lists.newArrayList(guild)); - } - return true; + guildsById.clear(); + guildByPlayer.clear(); + guilds.forEach(this::addGuild); + } + + public Optional guild(UUID playerUuid) + { + UUID guildUuid = guildByPlayer.get(playerUuid); + return guildUuid == null ? Optional.empty() : Optional.ofNullable(guildsById.get(guildUuid)); + } + + public Optional guildById(UUID guildUuid) + { + return Optional.ofNullable(guildsById.get(guildUuid)); + } + + public Optional guildByName(String name) + { + return guildsById.values().stream().filter(guild -> guild.getName().equalsIgnoreCase(name)).findFirst(); + } + + public Collection guilds() + { + return guildsById.values(); } public void addGuild(Guild guild) { - GUILDS.add(guild); + guildsById.put(guild.getGuildUuid(), guild); + guild.getMembers().stream().map(Member::getUuid).forEach(playerUuid -> indexMember(guild.getGuildUuid(), playerUuid)); } - public void deleteGuild(UUID owner) + public void removeGuild(UUID guildUuid) { - GUILDS.removeIf(guild -> guild.getOwner().getUuid().equals(owner)); + Guild removed = guildsById.remove(guildUuid); + if (removed != null) + { + removed.getMembers().stream().map(Member::getUuid).forEach(guildByPlayer::remove); + } + } + + public void indexMember(UUID guildUuid, UUID playerUuid) + { + guildByPlayer.put(playerUuid, guildUuid); + } + + public void unindexMember(UUID playerUuid) + { + guildByPlayer.remove(playerUuid); + } + + public void clear() + { + guildsById.clear(); + guildByPlayer.clear(); } public Optional getGuild(UUID uuid) { - return GUILDS.stream().filter(guild -> (guild.getOwner() != null && guild.getOwner().getUuid().equals(uuid)) || guild.getMembers().stream().map(Member::getUuid).toList().contains(uuid)).findFirst(); + return guild(uuid); } public Collection getGuilds() { - return GUILDS.stream().toList(); + return guilds(); } - } diff --git a/src/main/java/dev/plex/guild/data/GuildRole.java b/src/main/java/dev/plex/guild/data/GuildRole.java new file mode 100644 index 0000000..6d9f796 --- /dev/null +++ b/src/main/java/dev/plex/guild/data/GuildRole.java @@ -0,0 +1,7 @@ +package dev.plex.guild.data; + +public enum GuildRole +{ + OWNER, + MEMBER +} diff --git a/src/main/java/dev/plex/guild/data/Member.java b/src/main/java/dev/plex/guild/data/Member.java index fa60f6c..be84f56 100644 --- a/src/main/java/dev/plex/guild/data/Member.java +++ b/src/main/java/dev/plex/guild/data/Member.java @@ -10,8 +10,19 @@ import java.util.UUID; public class Member { private final UUID uuid; - private Rank rank; - private boolean chat, prefix; + private GuildRole role; + private boolean chat; + + public Member(UUID uuid) + { + this(uuid, GuildRole.MEMBER); + } + + public Member(UUID uuid, GuildRole role) + { + this.uuid = uuid; + this.role = role; + } public Player getPlayer() { diff --git a/src/main/java/dev/plex/guild/data/Permission.java b/src/main/java/dev/plex/guild/data/Permission.java deleted file mode 100644 index fede3e8..0000000 --- a/src/main/java/dev/plex/guild/data/Permission.java +++ /dev/null @@ -1,6 +0,0 @@ -package dev.plex.guild.data; - -public enum Permission -{ - SET_WARP, WARPS, DELETE_WARP, WARP -} diff --git a/src/main/java/dev/plex/guild/data/Rank.java b/src/main/java/dev/plex/guild/data/Rank.java deleted file mode 100644 index 7af27d0..0000000 --- a/src/main/java/dev/plex/guild/data/Rank.java +++ /dev/null @@ -1,5 +0,0 @@ -package dev.plex.guild.data; - -public record Rank(String name, String prefix) -{ -} diff --git a/src/main/java/dev/plex/storage/GuildRepository.java b/src/main/java/dev/plex/storage/GuildRepository.java new file mode 100644 index 0000000..1805cf2 --- /dev/null +++ b/src/main/java/dev/plex/storage/GuildRepository.java @@ -0,0 +1,41 @@ +package dev.plex.storage; + +import dev.plex.guild.Guild; +import dev.plex.guild.data.GuildRole; +import dev.plex.storage.entity.GuildInviteEntity; +import dev.plex.util.CustomLocation; +import org.bukkit.entity.Player; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface GuildRepository +{ + CompletableFuture> loadGuilds(); + + CompletableFuture createGuild(Player owner, String name); + + CompletableFuture deleteGuild(UUID guildUuid); + + CompletableFuture addMember(UUID guildUuid, UUID playerUuid, GuildRole role); + + CompletableFuture removeMember(UUID guildUuid, UUID playerUuid); + + CompletableFuture transferOwner(UUID guildUuid, UUID newOwnerUuid, UUID oldOwnerUuid); + + CompletableFuture updatePrefix(UUID guildUuid, String prefix); + + CompletableFuture updateHome(UUID guildUuid, CustomLocation home); + + CompletableFuture upsertWarp(UUID guildUuid, String name, CustomLocation location); + + CompletableFuture deleteWarp(UUID guildUuid, String name); + + CompletableFuture createInvite(UUID guildUuid, UUID inviterUuid, UUID inviteeUuid, Instant expiresAt); + + CompletableFuture deleteInvite(UUID guildUuid, UUID inviteeUuid); + + CompletableFuture> invitesFor(UUID inviteeUuid); +} diff --git a/src/main/java/dev/plex/storage/OrmGuildRepository.java b/src/main/java/dev/plex/storage/OrmGuildRepository.java new file mode 100644 index 0000000..36d88be --- /dev/null +++ b/src/main/java/dev/plex/storage/OrmGuildRepository.java @@ -0,0 +1,372 @@ +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 new file mode 100644 index 0000000..1628ae4 --- /dev/null +++ b/src/main/java/dev/plex/storage/entity/GuildEntity.java @@ -0,0 +1,58 @@ +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 new file mode 100644 index 0000000..48f85be --- /dev/null +++ b/src/main/java/dev/plex/storage/entity/GuildInviteEntity.java @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..dc4628e --- /dev/null +++ b/src/main/java/dev/plex/storage/entity/GuildMemberEntity.java @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..51f9f87 --- /dev/null +++ b/src/main/java/dev/plex/storage/entity/GuildWarpEntity.java @@ -0,0 +1,43 @@ +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 new file mode 100644 index 0000000..332b303 --- /dev/null +++ b/src/main/resources/db/migration/mariadb/001_initial_schema.sql @@ -0,0 +1,52 @@ +CREATE TABLE IF NOT EXISTS {{table:guilds}} ( + `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, + `public` BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (`guild_uuid`), + UNIQUE KEY `uq_guilds_name` (`name`) +); + +CREATE TABLE IF NOT EXISTS {{table:members}} ( + `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 {{table:warps}} ( + `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 {{table:invites}} ( + `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`) +); diff --git a/src/main/resources/db/migration/postgres/001_initial_schema.sql b/src/main/resources/db/migration/postgres/001_initial_schema.sql new file mode 100644 index 0000000..f8243c6 --- /dev/null +++ b/src/main/resources/db/migration/postgres/001_initial_schema.sql @@ -0,0 +1,47 @@ +CREATE TABLE IF NOT EXISTS {{table:guilds}} ( + 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, + public BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS {{table:members}} ( + 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 {{table:warps}} ( + 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 {{table:invites}} ( + 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) +); diff --git a/src/main/resources/db/migration/sqlite/001_initial_schema.sql b/src/main/resources/db/migration/sqlite/001_initial_schema.sql new file mode 100644 index 0000000..f054cec --- /dev/null +++ b/src/main/resources/db/migration/sqlite/001_initial_schema.sql @@ -0,0 +1,47 @@ +CREATE TABLE IF NOT EXISTS {{table:guilds}} ( + 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, + public BOOLEAN NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS {{table:members}} ( + 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 {{table:warps}} ( + 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 {{table:invites}} ( + 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) +); diff --git a/src/main/resources/guilds/messages.yml b/src/main/resources/guilds/messages.yml index a2dae05..0f9d2c6 100644 --- a/src/main/resources/guilds/messages.yml +++ b/src/main/resources/guilds/messages.yml @@ -4,6 +4,7 @@ guildCommandNotFound: "'{0}' is not a valid sub command!" guildNotFound: "You're currently not a part of a guild!" guildInThis: "You're currently a part of this guild!" alreadyInGuild: "You're currently in a guild. Please do /guild leave if you're a member, or if you're an owner with members, /guild promote then /guild leave, or just an owner, /guild disband." +guildCreated: "Created guild named {0}." guildNotOwner: "You're not the owner of this guild!" guildMemberNotFound: "This guild member could not be found!" guildOwnerSet: "You have successfully promoted {0} to be the new guild owner. You have been set to a default guild member." @@ -23,8 +24,16 @@ guildNoInvite: "You don't have any pending invitations!" guildNotValidInvite: "You don't have an invite from this guild!" guildInviteExists: "You've already sent an invite to this person!" guildInviteSent: "You have sent an invite to {0}" -guildInviteReceived: "You have received an invite from {0} for the guild {1}[ACCEPT]You may also run /guild invite accept {1} to accept this invite. It will expire in 5 minutes" +guildInviteReceived: "You have received an invite from {0} for the guild {1}[ACCEPT]You may also run /guild accept {1} to accept this invite. It will expire in 5 minutes" guildMemberJoined: "{0} has joined the guild!" guildMemberLeft: "{0} has left the guild!" guildDisbandNeeded: "You need to disband your guild using /guild disband or promote a new owner using /guild owner " guildAutoDisbanded: "Auto-disbanding your guild since there were no members" +guildDisbanded: "Your guild has been disbanded." +guildLeft: "You have left the guild." +guildOwnerLeaveBlocked: "You must transfer ownership before leaving this guild." +guildInviteDenied: "You denied the guild invite." +guildInviteExpired: "This guild invite has expired." +guildTargetAlreadyInGuild: "That player is already in a guild." +guildCannotInviteSelf: "You cannot invite yourself." +guildStorageFailed: "The guild storage operation failed."