diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/configuration/implementation/PluginConfiguration.java b/eternalcore-core/src/main/java/com/eternalcode/core/configuration/implementation/PluginConfiguration.java index 3dd6c217b..a1969604d 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/configuration/implementation/PluginConfiguration.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/configuration/implementation/PluginConfiguration.java @@ -3,6 +3,8 @@ import com.eternalcode.core.configuration.AbstractConfigurationFile; import com.eternalcode.core.database.DatabaseConfig; import com.eternalcode.core.database.DatabaseSettings; +import com.eternalcode.core.util.date.DateConfig; +import com.eternalcode.core.util.date.DateSettings; import com.eternalcode.core.feature.afk.AfkConfig; import com.eternalcode.core.feature.afk.AfkSettings; import com.eternalcode.core.feature.automessage.AutoMessageConfig; @@ -50,21 +52,20 @@ import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; import eu.okaeri.configs.annotation.Header; -import org.bukkit.Sound; import java.io.File; @Header({ - "#", - "# This is the main configuration file for EternalCore.", - "#", - "# If you need help with the configuration or have any questions related to EternalCore, join our discord, or create an issue on our GitHub.", - "#", - "# Issues: https://github.com/EternalCodeTeam/EternalCore/issues", - "# Discord: https://discord.gg/FQ7jmGBd6c", - "# Website: https://eternalcode.pl/", - "# Source Code: https://github.com/EternalCodeTeam/EternalCore", - "#", + "#", + "# This is the main configuration file for EternalCore.", + "#", + "# If you need help with the configuration or have any questions related to EternalCore, join our discord, or create an issue on our GitHub.", + "#", + "# Issues: https://github.com/EternalCodeTeam/EternalCore/issues", + "# Discord: https://discord.gg/FQ7jmGBd6c", + "# Website: https://eternalcode.pl/", + "# Source Code: https://github.com/EternalCodeTeam/EternalCore", + "#", }) @ConfigurationFile public class PluginConfiguration extends AbstractConfigurationFile { @@ -85,6 +86,12 @@ public class PluginConfiguration extends AbstractConfigurationFile { @Comment("# Settings responsible for the database connection") DatabaseConfig database = new DatabaseConfig(); + @Bean(proxied = DateSettings.class) + @Comment("") + @Comment("# Date Configuration") + @Comment("# Settings for date formatting") + DateConfig date = new DateConfig(); + @Bean(proxied = SpawnJoinSettings.class) @Comment("") @Comment("# Spawn & Join Configuration") diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkKickController.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkKickController.java index c1bc2c32a..fd9a1441b 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkKickController.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkKickController.java @@ -9,6 +9,7 @@ import com.eternalcode.core.translation.TranslationManager; import com.eternalcode.core.user.User; import com.eternalcode.core.user.UserManager; +import java.util.Optional; import java.util.UUID; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -56,7 +57,8 @@ void onAfkSwitch(AfkSwitchEvent event) { return; } - User user = this.userManager.getOrCreate(playerUUID, player.getName()); + Optional optionalUser = this.userManager.getUser(playerUUID); + User user = optionalUser.get(); Translation translation = this.translationManager.getMessages(user.getUniqueId()); Component component = this.miniMessage.deserialize(translation.afk().afkKickReason()); diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/msg/MsgServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/msg/MsgServiceImpl.java index ae9b4566c..fd7be52b4 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/msg/MsgServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/msg/MsgServiceImpl.java @@ -13,9 +13,9 @@ import com.google.common.cache.CacheBuilder; import java.time.Duration; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import java.util.UUID; +import org.bukkit.Server; import org.bukkit.entity.Player; @Service @@ -27,6 +27,7 @@ class MsgServiceImpl implements MsgService { private final MsgPresenter presenter; private final EventCaller eventCaller; private final MsgToggleService msgToggleService; + private final Server server; private final Cache replies = CacheBuilder.newBuilder() .expireAfterWrite(Duration.ofHours(1)) @@ -40,7 +41,7 @@ class MsgServiceImpl implements MsgService { IgnoreService ignoreService, UserManager userManager, EventCaller eventCaller, - MsgToggleService msgToggleService + MsgToggleService msgToggleService, Server server ) { this.noticeService = noticeService; this.ignoreService = ignoreService; @@ -49,15 +50,31 @@ class MsgServiceImpl implements MsgService { this.msgToggleService = msgToggleService; this.presenter = new MsgPresenter(noticeService); + this.server = server; } - void privateMessage(User sender, User target, String message) { - if (target.getClientSettings().isOffline()) { + @Override + public void reply(Player sender, String message) { + UUID uuid = this.replies.getIfPresent(sender.getUniqueId()); + + if (uuid == null) { + this.noticeService.player(sender.getUniqueId(), translation -> translation.msg().noReply()); + + return; + } + + Player target = this.server.getPlayer(uuid); + if (target == null) { this.noticeService.player(sender.getUniqueId(), translation -> translation.argument().offlinePlayer()); return; } + this.sendMessage(sender, target, message); + } + + @Override + public void sendMessage(Player sender, Player target, String message) { UUID uniqueId = target.getUniqueId(); this.msgToggleService.getState(uniqueId).thenAccept(msgState -> { @@ -75,33 +92,11 @@ void privateMessage(User sender, User target, String message) { MsgEvent event = new MsgEvent(sender.getUniqueId(), uniqueId, message); this.eventCaller.callEvent(event); - this.presenter.onMessage(new Message(sender, target, event.getContent(), this.socialSpy, isIgnored)); + this.presenter.onMessage(new Message(toUser(sender), toUser(target), event.getContent(), this.socialSpy, isIgnored)); }); }); } - void reply(User sender, String message) { - UUID uuid = this.replies.getIfPresent(sender.getUniqueId()); - - if (uuid == null) { - this.noticeService.player(sender.getUniqueId(), translation -> translation.msg().noReply()); - - return; - } - - Optional targetOption = this.userManager.getUser(uuid); - - if (targetOption.isEmpty()) { - this.noticeService.player(sender.getUniqueId(), translation -> translation.argument().offlinePlayer()); - - return; - } - - User target = targetOption.get(); - - this.privateMessage(sender, target, message); - } - @Override public void enableSpy(UUID player) { this.socialSpy.add(player); @@ -117,14 +112,9 @@ public boolean isSpy(UUID player) { return this.socialSpy.contains(player); } - @Override - public void reply(Player sender, String message) { - this.reply(this.userManager.getOrCreate(sender.getUniqueId(), sender.getName()), message); + private User toUser(Player target) { + return this.userManager.getUser(target.getUniqueId()) + .orElseThrow(() -> new IllegalStateException("User not found for player " + target.getName())); } - @Override - public void sendMessage(Player sender, Player target, String message) { - User user = this.userManager.getOrCreate(target.getUniqueId(), target.getName()); - this.privateMessage(this.userManager.getOrCreate(sender.getUniqueId(), sender.getName()), user, message); - } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/ENWhoIsMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/ENWhoIsMessages.java index ba9a930fc..dd0984856 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/ENWhoIsMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/ENWhoIsMessages.java @@ -9,28 +9,31 @@ @Getter @Accessors(fluent = true) public class ENWhoIsMessages extends OkaeriConfig implements WhoIsMessages { - + @Comment({ - " ", - "# {PLAYER} - Player name", - "# {UUID} - Player UUID", - "# {IP} - Player IP", - "# {WALK-SPEED} - Player walk speed", - "# {SPEED} - Player fly speed", - "# {PING} - Player ping", - "# {LEVEL} - Player level", - "# {HEALTH} - Player health", - "# {FOOD} - Player food level" + " ", + "# {PLAYER} - Player name", + "# {UUID} - Player UUID", + "# {IP} - Player IP", + "# {WALK-SPEED} - Player walk speed", + "# {SPEED} - Player fly speed", + "# {PING} - Player ping", + "# {LEVEL} - Player level", + "# {HEALTH} - Player health", + "# {FOOD} - Player food level", + "# {LAST-SEEN} - Player last seen", + "# {ACCOUNT-CREATED} - Player account created" }) public List info = List.of( - "Target name: {PLAYER}", - "Target UUID: {UUID}", - "Target address: {IP}", - "Target walk speed: {WALK-SPEED}", - "Target fly speed: {SPEED}", - "Target ping: {PING}ms", - "Target level: {LEVEL}", - "Target health: {HEALTH}", - "Target food level: {FOOD}" - ); + "Target name: {PLAYER}", + "Target UUID: {UUID}", + "Target address: {IP}", + "Target walk speed: {WALK-SPEED}", + "Target fly speed: {SPEED}", + "Target ping: {PING}ms", + "Target level: {LEVEL}", + "Target health: {HEALTH}", + "Target food level: {FOOD}", + "Last seen: {LAST-SEEN}", + "Account created: {ACCOUNT-CREATED}"); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/PLWhoIsMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/PLWhoIsMessages.java index 279ba5b2d..d8b0be334 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/PLWhoIsMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/PLWhoIsMessages.java @@ -9,28 +9,31 @@ @Getter @Accessors(fluent = true) public class PLWhoIsMessages extends OkaeriConfig implements WhoIsMessages { - + @Comment({ - " ", - "# {PLAYER} - nazwa gracza", - "# {UUID} - UUID gracza", - "# {IP} - IP gracza", - "# {WALK-SPEED} - prędkość chodzenia gracza", - "# {SPEED} - prędkość latania gracza", - "# {PING} - ping gracza", - "# {LEVEL} - poziom gracza", - "# {HEALTH} - zdrowie gracza", - "# {FOOD} - poziom najedzenia gracza" + " ", + "# {PLAYER} - nazwa gracza", + "# {UUID} - UUID gracza", + "# {IP} - IP gracza", + "# {WALK-SPEED} - prędkość chodzenia gracza", + "# {SPEED} - prędkość latania gracza", + "# {PING} - ping gracza", + "# {LEVEL} - poziom gracza", + "# {HEALTH} - zdrowie gracza", + "# {FOOD} - poziom najedzenia gracza", + "# {LAST-SEEN} - Ostatnio widziany", + "# {ACCOUNT-CREATED} - Data utworzenia konta" }) public List info = List.of( - "Gracz: {PLAYER}", - "UUID: {UUID}", - "IP: {IP}", - "Szybkość chodzenia: {WALK-SPEED}", - "Szybkość latania: {SPEED}", - "Opóźnienie: {PING}ms", - "Poziom: {LEVEL}", - "Zdrowie: {HEALTH}", - "Poziom najedzenia: {FOOD}" - ); + "Gracz: {PLAYER}", + "UUID: {UUID}", + "IP: {IP}", + "Szybkość chodzenia: {WALK-SPEED}", + "Szybkość latania: {SPEED}", + "Opóźnienie: {PING}ms", + "Poziom: {LEVEL}", + "Zdrowie: {HEALTH}", + "Poziom najedzenia: {FOOD}", + "Ostatnio widziany: {LAST-SEEN}", + "Data utworzenia konta: {ACCOUNT-CREATED}"); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/WhoIsCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/WhoIsCommand.java index 4cc9cc93b..a85f522f7 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/WhoIsCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/whois/WhoIsCommand.java @@ -1,8 +1,10 @@ package com.eternalcode.core.feature.whois; import com.eternalcode.annotations.scan.command.DescriptionDocs; +import com.eternalcode.core.util.date.DateFormatter; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.notice.NoticeService; +import com.eternalcode.core.user.UserManager; import com.eternalcode.core.viewer.Viewer; import dev.rollczi.litecommands.annotations.argument.Arg; import dev.rollczi.litecommands.annotations.command.Command; @@ -16,27 +18,34 @@ class WhoIsCommand { private final NoticeService noticeService; + private final UserManager userManager; + private final DateFormatter dateFormatter; @Inject - WhoIsCommand(NoticeService noticeService) { + WhoIsCommand(NoticeService noticeService, UserManager userManager, DateFormatter dateFormatter) { this.noticeService = noticeService; + this.userManager = userManager; + this.dateFormatter = dateFormatter; } @Execute @DescriptionDocs(description = "Shows information about player", arguments = "") void execute(@Sender Viewer viewer, @Arg Player player) { - this.noticeService.create() - .placeholder("{PLAYER}", player.getName()) - .placeholder("{UUID}", String.valueOf(player.getUniqueId())) - .placeholder("{IP}", player.getAddress().getHostString()) - .placeholder("{WALK-SPEED}", String.valueOf(player.getWalkSpeed())) - .placeholder("{SPEED}", String.valueOf(player.getFlySpeed())) - .placeholder("{PING}", String.valueOf(player.getPing())) - .placeholder("{LEVEL}", String.valueOf(player.getLevel())) - .placeholder("{HEALTH}", String.valueOf(Math.round(player.getHealthScale()))) - .placeholder("{FOOD}", String.valueOf(player.getFoodLevel())) - .messages(translation -> translation.whois().info()) - .viewer(viewer) - .send(); + this.userManager.findOrCreate(player.getUniqueId(), player.getName()) + .thenAccept(user -> this.noticeService.create() + .placeholder("{PLAYER}", player.getName()) + .placeholder("{UUID}", String.valueOf(player.getUniqueId())) + .placeholder("{IP}", player.getAddress().getHostString()) + .placeholder("{WALK-SPEED}", String.valueOf(player.getWalkSpeed())) + .placeholder("{SPEED}", String.valueOf(player.getFlySpeed())) + .placeholder("{PING}", String.valueOf(player.getPing())) + .placeholder("{LEVEL}", String.valueOf(player.getLevel())) + .placeholder("{HEALTH}", String.valueOf(Math.round(player.getHealthScale()))) + .placeholder("{FOOD}", String.valueOf(player.getFoodLevel())) + .placeholder("{LAST-SEEN}", this.dateFormatter.format(user.getLastSeen())) + .placeholder("{ACCOUNT-CREATED}", this.dateFormatter.format(user.getAccountCreated())) + .messages(translation -> translation.whois().info()) + .viewer(viewer) + .send()); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/LoadUserController.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/LoadUserController.java deleted file mode 100644 index db8e33b79..000000000 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/LoadUserController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.eternalcode.core.user; - -import com.eternalcode.core.injector.annotations.Inject; -import com.eternalcode.core.injector.annotations.component.Controller; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.server.ServerLoadEvent; - -@Controller -class LoadUserController implements Listener { - - private final UserManager userManager; - private final Server server; - - @Inject - LoadUserController(UserManager userManager, Server server) { - this.userManager = userManager; - this.server = server; - } - - @EventHandler - void onReload(ServerLoadEvent event) { - if (event.getType() != ServerLoadEvent.LoadType.RELOAD) { - return; - } - - for (Player player : this.server.getOnlinePlayers()) { - this.userManager.create(player.getUniqueId(), player.getName()); - } - } - -} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/PrepareUserController.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/PrepareUserController.java deleted file mode 100644 index 890667def..000000000 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/PrepareUserController.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.eternalcode.core.user; - -import com.eternalcode.core.injector.annotations.Inject; -import com.eternalcode.core.injector.annotations.component.Controller; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerKickEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -@Controller -class PrepareUserController implements Listener { - - private final UserManager userManager; - private final Server server; - - @Inject - PrepareUserController(UserManager userManager, Server server) { - this.userManager = userManager; - this.server = server; - } - - @EventHandler - void onJoin(PlayerJoinEvent event) { - Player player = event.getPlayer(); - User user = this.userManager.getOrCreate(player.getUniqueId(), player.getName()); - UserClientBukkitSettings clientSettings = new UserClientBukkitSettings(this.server, user.getUniqueId()); - - user.setClientSettings(clientSettings); - } - - @EventHandler - void onQuit(PlayerQuitEvent event) { - Player player = event.getPlayer(); - - User user = this.userManager.getUser(player.getUniqueId()) - .orElseThrow(() -> new IllegalStateException("User not found")); - - user.setClientSettings(UserClientSettings.NONE); - } - - @EventHandler - void onKick(PlayerKickEvent event) { - Player player = event.getPlayer(); - - User user = this.userManager.getUser(player.getUniqueId()) - .orElseThrow(() -> new IllegalStateException("User not found")); - - user.setClientSettings(UserClientSettings.NONE); - } -} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/User.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/User.java index 0536b877a..73e5dfe49 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/User.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/User.java @@ -2,19 +2,22 @@ import com.eternalcode.core.viewer.Viewer; +import java.time.Instant; import java.util.Objects; import java.util.UUID; public class User implements Viewer { - private UserClientSettings userClientSettings = UserClientSettings.NONE; - private final String name; private final UUID uuid; + private final Instant lastSeen; + private final Instant accountCreated; - User(UUID uuid, String name) { + public User(UUID uuid, String name, Instant lastSeen, Instant accountCreated) { this.name = name; this.uuid = uuid; + this.lastSeen = lastSeen; + this.accountCreated = accountCreated; } @Override @@ -27,17 +30,21 @@ public UUID getUniqueId() { return this.uuid; } - @Override - public boolean isConsole() { - return false; + public Instant getLastSeen() { + return this.lastSeen; + } + + public Instant getAccountCreated() { + return this.accountCreated; } - public UserClientSettings getClientSettings() { - return this.userClientSettings; + public User updateLastSeen(Instant lastSeen) { + return new User(this.uuid, this.name, lastSeen, this.accountCreated); } - public void setClientSettings(UserClientSettings userClientSettings) { - this.userClientSettings = userClientSettings; + @Override + public boolean isConsole() { + return false; } @Override @@ -48,12 +55,14 @@ public boolean equals(Object o) { if (!(o instanceof User user)) { return false; } - return this.name.equals(user.name) && this.uuid.equals(user.uuid); + return this.name.equals(user.name) && + this.uuid.equals(user.uuid) && + Objects.equals(this.lastSeen, user.lastSeen) && + Objects.equals(this.accountCreated, user.accountCreated); } @Override public int hashCode() { - return Objects.hash(this.name, this.uuid); + return Objects.hash(this.name, this.uuid, this.lastSeen, this.accountCreated); } } - diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientBukkitSettings.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientBukkitSettings.java deleted file mode 100644 index 2a45d8aae..000000000 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientBukkitSettings.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.eternalcode.core.user; - -import org.bukkit.Server; -import org.bukkit.entity.Player; - -import java.lang.ref.WeakReference; -import java.util.Optional; -import java.util.UUID; - -class UserClientBukkitSettings implements UserClientSettings { - - private final Server server; - private final UUID uuid; - private WeakReference playerReference; - - UserClientBukkitSettings(Server server, UUID uuid) { - this.server = server; - this.uuid = uuid; - this.playerReference = new WeakReference<>(server.getPlayer(uuid)); - } - - @Override - public boolean isOnline() { - return this.getPlayer().isPresent(); - } - - private Optional getPlayer() { - Player player = this.playerReference.get(); - - if (player == null) { - Player playerFromServer = this.server.getPlayer(this.uuid); - - if (playerFromServer == null) { - return Optional.empty(); - } - - this.playerReference = new WeakReference<>(playerFromServer); - return Optional.of(playerFromServer); - } - - return Optional.of(player); - } - -} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientNoneSettings.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientNoneSettings.java deleted file mode 100644 index 62fdb60a0..000000000 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientNoneSettings.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.eternalcode.core.user; - -class UserClientNoneSettings implements UserClientSettings { - - @Override - public boolean isOnline() { - return false; - } - - -} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientSettings.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientSettings.java deleted file mode 100644 index 7a0012bf3..000000000 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserClientSettings.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.eternalcode.core.user; - -public interface UserClientSettings { - - UserClientSettings NONE = new UserClientNoneSettings(); - - boolean isOnline(); - - default boolean isOffline() { - return !this.isOnline(); - } - -} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserController.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserController.java new file mode 100644 index 000000000..8c15320ed --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserController.java @@ -0,0 +1,56 @@ +package com.eternalcode.core.user; + +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Controller; +import java.util.function.BiConsumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.ServerLoadEvent; + +@Controller +class UserController implements Listener { + + private final UserManager userManager; + private final Server server; + private final Logger logger; + + @Inject + UserController(UserManager userManager, Server server, Logger logger) { + this.userManager = userManager; + this.server = server; + this.logger = logger; + } + + @EventHandler(priority = EventPriority.LOWEST) + void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + this.userManager.findOrCreate(player.getUniqueId(), player.getName()) + .whenComplete(this.handleFutureResult(player, "Failed to load user: " + player.getName() + ". Please try again.")); + } + + private BiConsumer handleFutureResult(Player player, String message) { + return (user, throwable) -> { + if (throwable != null) { + player.kickPlayer(message); + this.logger.log(Level.SEVERE, message, throwable); + } + }; + } + + @EventHandler + void onReload(ServerLoadEvent event) { + if (event.getType() != ServerLoadEvent.LoadType.RELOAD) { + return; + } + + for (Player player : this.server.getOnlinePlayers()) { + this.userManager.findOrCreate(player.getUniqueId(), player.getName()); + } + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManager.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManager.java index e6bcfc0f2..02f391ea8 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManager.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManager.java @@ -1,56 +1,81 @@ package com.eternalcode.core.user; +import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; -import java.util.Collection; -import java.util.Collections; +import com.eternalcode.core.user.database.UserRepository; +import java.time.Instant; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @Service public class UserManager { - private final Map usersByUUID = new ConcurrentHashMap<>(); + private final Map usersByUniqueId = new ConcurrentHashMap<>(); private final Map usersByName = new ConcurrentHashMap<>(); + private final UserRepository userRepository; - public Optional getUser(UUID uuid) { - return Optional.ofNullable(this.usersByUUID.get(uuid)); + @Inject + public UserManager(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public Optional getUser(UUID uniqueId) { + return Optional.ofNullable(this.usersByUniqueId.get(uniqueId)); } public Optional getUser(String name) { return Optional.ofNullable(this.usersByName.get(name)); } - public User getOrCreate(UUID uuid, String name) { - User userByUUID = this.usersByUUID.get(uuid); + public CompletableFuture findOrCreate(UUID uniqueId, String name) { + User cached = this.usersByUniqueId.get(uniqueId); - if (userByUUID != null) { - return userByUUID; + if (cached != null) { + this.updateNameIfChanged(cached, name); + return CompletableFuture.completedFuture(this.updateLastSeen(cached)); } - User userByName = this.usersByName.get(name); + return this.userRepository.getUser(uniqueId) + .thenApply(optionalUser -> { + User user = optionalUser + .map(existing -> { + this.updateNameIfChanged(existing, name); + return this.updateLastSeen(existing); + }) + .orElseGet(() -> this.createNewUser(uniqueId, name)); - if (userByName != null) { - return userByName; - } + this.add(user); + return user; + }); + } - return this.create(uuid, name); + private User createNewUser(UUID uniqueId, String name) { + Instant now = Instant.now(); + User user = new User(uniqueId, name, now, now); + this.userRepository.saveUser(user); + return user; } - public User create(UUID uuid, String name) { - if (this.usersByUUID.containsKey(uuid) || this.usersByName.containsKey(name)) { - throw new IllegalStateException("User already exists"); + private void updateNameIfChanged(User user, String name) { + if (!user.getName().equals(name)) { + User updated = new User(user.getUniqueId(), name, user.getLastSeen(), user.getAccountCreated()); + this.add(updated); + this.userRepository.saveUser(updated); } + } - User user = new User(uuid, name); - this.usersByUUID.put(uuid, user); - this.usersByName.put(name, user); - - return user; + private User updateLastSeen(User user) { + User updated = user.updateLastSeen(Instant.now()); + this.add(updated); + this.userRepository.saveUser(updated); + return updated; } - public Collection getUsers() { - return Collections.unmodifiableCollection(this.usersByUUID.values()); + private void add(User user) { + this.usersByUniqueId.put(user.getUniqueId(), user); + this.usersByName.put(user.getName(), user); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java new file mode 100644 index 000000000..847c85631 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java @@ -0,0 +1,16 @@ +package com.eternalcode.core.user.database; + +import com.eternalcode.core.user.User; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface UserRepository { + + CompletableFuture> getUser(UUID uniqueId); + + CompletableFuture> getUser(String name); + + CompletableFuture saveUser(User user); + +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java new file mode 100644 index 000000000..00964c3c4 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java @@ -0,0 +1,51 @@ +package com.eternalcode.core.user.database; + +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.core.database.AbstractRepositoryOrmLite; +import com.eternalcode.core.database.DatabaseManager; +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Repository; +import com.eternalcode.core.user.User; +import com.j256.ormlite.table.TableUtils; +import java.sql.SQLException; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@Repository +public class UserRepositoryImpl extends AbstractRepositoryOrmLite implements UserRepository { + + private static final String NAME_COLUMN = "name"; + + @Inject + public UserRepositoryImpl(DatabaseManager databaseManager, Scheduler scheduler) throws SQLException { + super(databaseManager, scheduler); + TableUtils.createTableIfNotExists(databaseManager.connectionSource(), UserTable.class); + } + + @Override + public CompletableFuture> getUser(UUID uniqueId) { + return this.selectSafe(UserTable.class, uniqueId) + .thenApply(optional -> optional.map(UserTable::toUser)); + } + + @Override + public CompletableFuture> getUser(String name) { + return this.action( + UserTable.class, + dao -> Optional.ofNullable( + dao.queryBuilder() + .where() + .eq(NAME_COLUMN, name) + .queryForFirst() + ).map(UserTable::toUser) + ); + } + + @Override + public CompletableFuture saveUser(User user) { + return this.save(UserTable.class, UserTable.from(user)) + .thenApply(status -> null); + } + +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java new file mode 100644 index 000000000..0874fa8e2 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java @@ -0,0 +1,42 @@ +package com.eternalcode.core.user.database; + +import com.eternalcode.core.user.User; +import com.j256.ormlite.field.DataType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; +import java.time.Instant; +import java.util.UUID; + +@DatabaseTable(tableName = "eternal_core_users") +class UserTable { + + @DatabaseField(columnName = "id", id = true) + private UUID uniqueId; + + @DatabaseField(columnName = "name") + private String name; + + @DatabaseField(columnName = "last_seen", dataType = DataType.SERIALIZABLE) + private Instant lastSeen; + + @DatabaseField(columnName = "account_created", dataType = DataType.SERIALIZABLE) + private Instant accountCreated; + + UserTable() { + } + + UserTable(UUID uniqueId, String name, Instant lastSeen, Instant accountCreated) { + this.uniqueId = uniqueId; + this.name = name; + this.lastSeen = lastSeen; + this.accountCreated = accountCreated; + } + + User toUser() { + return new User(this.uniqueId, this.name, this.lastSeen, this.accountCreated); + } + + static UserTable from(User user) { + return new UserTable(user.getUniqueId(), user.getName(), user.getLastSeen(), user.getAccountCreated()); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateConfig.java b/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateConfig.java new file mode 100644 index 000000000..246e4a125 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateConfig.java @@ -0,0 +1,21 @@ +package com.eternalcode.core.util.date; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import lombok.Getter; +import lombok.experimental.Accessors; + +@Getter +@Accessors(fluent = true) +public class DateConfig extends OkaeriConfig implements DateSettings { + + @Comment({ + "# Date format used in the plugin", + "# You can use standard Java date format patterns", + "# Examples:", + "# yyyy-MM-dd HH:mm:ss - 2024-12-06 14:30:00", + "# dd.MM.yyyy HH:mm - 06.12.2024 14:30" + }) + public String format = "yyyy-MM-dd HH:mm:ss"; + +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateFormatter.java b/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateFormatter.java new file mode 100644 index 000000000..c17cd4fee --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateFormatter.java @@ -0,0 +1,9 @@ +package com.eternalcode.core.util.date; + +import java.time.Instant; + +public interface DateFormatter { + + String format(Instant instant); + +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateFormatterImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateFormatterImpl.java new file mode 100644 index 000000000..5bba05672 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateFormatterImpl.java @@ -0,0 +1,37 @@ +package com.eternalcode.core.util.date; + +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Service; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +@Service +public class DateFormatterImpl implements DateFormatter { + + private final DateSettings dateSettings; + private DateTimeFormatter cachedFormatter; + private String lastFormat; + + @Inject + public DateFormatterImpl(DateSettings dateSettings) { + this.dateSettings = dateSettings; + } + + @Override + public String format(Instant instant) { + if (instant == null) { + return "N/A"; + } + + String currentFormat = this.dateSettings.format(); + if (this.cachedFormatter == null || !currentFormat.equals(this.lastFormat)) { + this.lastFormat = currentFormat; + this.cachedFormatter = DateTimeFormatter.ofPattern(currentFormat) + .withZone(ZoneId.systemDefault()); + } + + return this.cachedFormatter.format(instant); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateSettings.java b/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateSettings.java new file mode 100644 index 000000000..babe7692d --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/util/date/DateSettings.java @@ -0,0 +1,6 @@ +package com.eternalcode.core.util.date; + +public interface DateSettings { + + String format(); +} diff --git a/eternalcore-core/src/test/java/com/eternalcode/core/user/PrepareUserControllerTest.java b/eternalcore-core/src/test/java/com/eternalcode/core/user/PrepareUserControllerTest.java deleted file mode 100644 index 2b0be982a..000000000 --- a/eternalcore-core/src/test/java/com/eternalcode/core/user/PrepareUserControllerTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.eternalcode.core.user; - -import com.eternalcode.core.test.MockServer; -import org.bukkit.entity.Player; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static com.eternalcode.core.test.OptionAssertions.assertOptionEmpty; -import static com.eternalcode.core.test.OptionAssertions.assertOptionPresent; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class PrepareUserControllerTest { - - private static final UUID PLAYER_UUID = UUID.randomUUID(); - private static final String PLAYER_NAME = "Martin"; - - private MockServer mockServer; - private UserManager userManager; - - @BeforeEach - void setUp() { - this.mockServer = new MockServer(); - this.userManager = new UserManager(); - - PrepareUserController controller = new PrepareUserController(this.userManager, this.mockServer.getServer()); - - this.mockServer.listenJoin(controller::onJoin); - this.mockServer.listenQuit(controller::onQuit); - this.mockServer.listenKick(controller::onKick); - } - - @Test - @DisplayName("should create user and set ClientBukkitSettings for user") - void testJoinPlayer() { - assertOptionEmpty(this.userManager.getUser(PLAYER_UUID)); - - this.mockServer.joinPlayer(PLAYER_NAME, PLAYER_UUID); - User user = assertOptionPresent(this.userManager.getUser(PLAYER_UUID)); - - UserClientSettings userClientSettings = user.getClientSettings(); - assertTrue(userClientSettings.isOnline()); - assertInstanceOf(UserClientBukkitSettings.class, userClientSettings); - } - - @Test - @DisplayName("should create user and reset client settings to NONE after quit") - void testPlayerQuit() { - Player player = this.mockServer.joinPlayer(PLAYER_NAME, PLAYER_UUID); - User user = assertOptionPresent(this.userManager.getUser(PLAYER_UUID)); - - this.mockServer.quitPlayer(player); - - UserClientSettings userClientSettings = user.getClientSettings(); - assertTrue(userClientSettings.isOffline()); - assertEquals(UserClientSettings.NONE, userClientSettings); - } - - @Test - @DisplayName("should create user and reset client settings to NONE after kick") - void testPlayerKick() { - Player player = this.mockServer.joinPlayer(PLAYER_NAME, PLAYER_UUID); - User user = assertOptionPresent(this.userManager.getUser(PLAYER_UUID)); - - this.mockServer.kickPlayer(player); - - UserClientSettings userClientSettings = user.getClientSettings(); - assertTrue(userClientSettings.isOffline()); - assertEquals(UserClientSettings.NONE, userClientSettings); - } - -} diff --git a/eternalcore-core/src/test/java/com/eternalcode/core/user/UserManagerTest.java b/eternalcore-core/src/test/java/com/eternalcode/core/user/UserManagerTest.java deleted file mode 100644 index 1c4c3fcfa..000000000 --- a/eternalcore-core/src/test/java/com/eternalcode/core/user/UserManagerTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.eternalcode.core.user; - -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class UserManagerTest { - - @Test - void testUsersCreate() { - UserManager manager = new UserManager(); - - assertEquals(0, manager.getUsers().size()); - - manager.create(UUID.randomUUID(), "Piotr"); - assertEquals(1, manager.getUsers().size()); - - manager.create(UUID.randomUUID(), "Igor"); - manager.create(UUID.randomUUID(), "Norbert"); - manager.create(UUID.randomUUID(), "Martin"); - assertEquals(4, manager.getUsers().size()); - } - - @Test - void testCreateSameUser() { - UserManager manager = new UserManager(); - - manager.create(UUID.randomUUID(), "Piotr"); - assertThrows(IllegalStateException.class, () -> manager.create(UUID.randomUUID(), "Piotr")); - - UUID uuid = UUID.randomUUID(); - manager.create(uuid, "Rollczi"); - assertThrows(IllegalStateException.class, () -> manager.create(uuid, "Lucky")); - } - - @Test - void testGetUsers() { - UserManager manager = new UserManager(); - - assertEquals(0, manager.getUsers().size()); - - UUID uuid = UUID.randomUUID(); - User user = manager.getOrCreate(uuid, "Paweł"); - - assertEquals(1, manager.getUsers().size()); - assertTrue(manager.getUsers().contains(user)); - } - - @Test - void testGetUser() { - UserManager manager = new UserManager(); - - assertEquals(0, manager.getUsers().size()); - - UUID uuid = UUID.randomUUID(); - manager.getOrCreate(uuid, "Adrian"); - - assertEquals(1, manager.getUsers().size()); - assertTrue(manager.getUser(uuid).isPresent()); - } - - @Test - void testGetOrCreate() { - UserManager manager = new UserManager(); - - UUID uuid = UUID.randomUUID(); - User user = manager.getOrCreate(uuid, "Michał"); - - assertEquals(user, manager.getOrCreate(uuid, "Michał")); - assertEquals(1, manager.getUsers().size()); - assertTrue(manager.getUsers().contains(user)); - - assertEquals(user, manager.getOrCreate(uuid, "Michał")); - } - -}