From b70ef08e0e96c277ebc42032f49732a22bdb150e Mon Sep 17 00:00:00 2001 From: Minecraft0122 <168195378+Minecraft0122@users.noreply.github.com> Date: Sun, 9 Aug 2026 16:41:52 +0800 Subject: [PATCH] Add registration-date player removal command --- .../djrapitops/plan/commands/PlanCommand.java | 15 +++- .../subcommands/DatabaseCommands.java | 70 +++++++++++++++++++ .../settings/locale/lang/CommandLang.java | 7 +- .../settings/locale/lang/DeepHelpLang.java | 1 + .../plan/settings/locale/lang/HelpLang.java | 7 +- .../queries/objects/BaseUserQueries.java | 11 ++- ...vePlayersRegisteredBetweenTransaction.java | 53 ++++++++++++++ .../assets/plan/locale/locale_EN.yml | 14 ++++ .../plan/commands/PlanCommandTest.java | 3 +- .../plan/storage/database/DatabaseTest.java | 16 +++++ 10 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemovePlayersRegisteredBetweenTransaction.java diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java index 697029163b..f2aee3af6b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/PlanCommand.java @@ -352,6 +352,7 @@ private Subcommand databaseCommand() { .subcommand(hotswapCommand()) .subcommand(clearCommand()) .subcommand(removeCommand()) + .subcommand(removeRegisteredBetweenCommand()) .subcommand(uninstalledCommand()) .subcommand(removeJoinAddressesCommand()) .subcommand(onlineUuidMigration()) @@ -478,6 +479,18 @@ private Subcommand clearCommand() { ).build(); } + private Subcommand removeRegisteredBetweenCommand() { + return Subcommand.builder() + .aliases("remove_registered", "removeregistered", "remove_between") + .requirePermission(Permissions.DATA_CLEAR) + .requiredArgument(locale.getString(HelpLang.ARG_AFTER_DATE), locale.getString(HelpLang.DESC_ARG_AFTER_DATE)) + .requiredArgument(locale.getString(HelpLang.ARG_BEFORE_DATE), locale.getString(HelpLang.DESC_ARG_BEFORE_DATE)) + .description(locale.getString(HelpLang.DB_REMOVE_REGISTERED)) + .inDepthDescription(locale.getString(DeepHelpLang.DB_REMOVE_REGISTERED)) + .onCommand(databaseCommands::onRemoveRegisteredBetween) + .build(); + } + private Subcommand removeCommand() { return Subcommand.builder() .aliases("remove") @@ -576,4 +589,4 @@ private Subcommand groups() { .onCommand(registrationCommands::onListWebGroups) .build(); } -} \ No newline at end of file +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java index ac051216c7..35e3690fbc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/commands/subcommands/DatabaseCommands.java @@ -58,6 +58,9 @@ import javax.inject.Singleton; import java.io.File; import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeParseException; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; @@ -413,6 +416,73 @@ private void performRemoval(CMDSender sender, Database database, UUID playerToRe } } + public void onRemoveRegisteredBetween(CMDSender sender, @Untrusted Arguments arguments) { + @Untrusted String afterDate = arguments.get(0) + .orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, 2, " "))); + @Untrusted String beforeDate = arguments.get(1) + .orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, 2, " "))); + + DateRange range = parseDateRange(afterDate, beforeDate, config.getTimeZone().toZoneId()); + Database database = dbSystem.getDatabase(); + String prompt = locale.getString(CommandLang.CONFIRM_REMOVE_PLAYERS_BETWEEN_DB, + afterDate, + beforeDate, + database.getType().getName()); + + confirmation.confirm(sender, prompt, choice -> { + if (Boolean.TRUE.equals(choice)) { + performRegisteredBetweenRemoval(sender, database, range); + } else { + sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_DATA)); + } + }); + } + + private DateRange parseDateRange(String afterDate, String beforeDate, ZoneId zoneId) { + try { + LocalDate after = LocalDate.parse(afterDate); + LocalDate before = LocalDate.parse(beforeDate); + if (after.isAfter(before)) { + throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_DATE_RANGE, afterDate, beforeDate)); + } + long afterTimestamp = after.atStartOfDay(zoneId).toInstant().toEpochMilli(); + long beforeTimestamp = before.plusDays(1L).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1L; + return new DateRange(afterTimestamp, beforeTimestamp); + } catch (DateTimeParseException invalidDate) { + throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_DATE_FORMAT, invalidDate.getParsedString())); + } + } + + private void performRegisteredBetweenRemoval(CMDSender sender, Database database, DateRange range) { + try { + sender.send(locale.getString(CommandLang.DB_REMOVAL_PLAYERS, database.getType().getName())); + RemovePlayersRegisteredBetweenTransaction transaction = new RemovePlayersRegisteredBetweenTransaction(range.after, range.before); + database.executeTransaction(transaction).join(); + + Set removedPlayerUUIDs = transaction.getRemovedPlayerUUIDs(); + removedPlayerUUIDs.forEach(queryService::playerRemoved); + sender.send(locale.getString(CommandLang.DB_REMOVAL_PLAYERS_SUCCESS, removedPlayerUUIDs.size())); + } catch (DBOpException e) { + sender.send(locale.getString(CommandLang.PROGRESS_FAIL, e.getMessage())); + errorLogger.error(e, ErrorContext.builder().related(sender, database.getType().getName(), range).build()); + } + } + + private static class DateRange { + private final long after; + private final long before; + + private DateRange(long after, long before) { + this.after = after; + this.before = before; + } + + @Override + public String toString() { + return after + "-" + before; + } + } + private void ensureDatabaseIsOpen() { Database.State dbState = dbSystem.getDatabase().getState(); if (dbState != Database.State.OPEN) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java index 24c599163e..c553793b9e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/CommandLang.java @@ -32,6 +32,7 @@ public enum CommandLang implements Lang { CONFIRM_MERGE_DB("command.confirmation.dbMerge", "You are about to merge data in ${0} to data in ${1}"), CONFIRM_CLEAR_DB("command.confirmation.dbClear", "Cmd Confirm - clearing db", "You are about to remove all Plan-data in ${0}"), CONFIRM_REMOVE_PLAYER_DB("command.confirmation.dbRemovePlayer", "Cmd Confirm - remove player db", "You are about to remove data of ${0} from ${1}"), + CONFIRM_REMOVE_PLAYERS_BETWEEN_DB("command.confirmation.dbRemovePlayersBetween", "You are about to remove players registered from ${0} through ${1} from ${2}"), CONFIRM_UNREGISTER("command.confirmation.unregister", "Cmd Confirm - unregister", "You are about to unregister '${0}' linked to ${1}"), CONFIRM_CANCELLED_DATA("command.confirmation.cancelNoChanges", "Cmd Confirm - cancelled, no data change", "Cancelled. No data was changed."), CONFIRM_CANCELLED_UNREGISTER("command.confirmation.cancelNoUnregister", "Cmd Confirm - cancelled, unregister", "Cancelled. '${0}' was not unregistered"), @@ -48,6 +49,8 @@ public enum CommandLang implements Lang { FAIL_USERNAME_NOT_KNOWN("command.fail.unknownUsername", "Cmd FAIL - Unknown Username", "§cUser has not been seen on this server"), FAIL_DATABASE_NOT_OPEN("command.database.failDbNotOpen", "Cmd FAIL - Database not open", "§cDatabase is ${0} - Please try again a bit later."), WARN_DATABASE_NOT_OPEN("command.database.warnDbNotOpen", "Cmd WARN - Database not open", "§eDatabase is ${0} - This might take longer than expected.."), + FAIL_DATE_FORMAT("command.fail.invalidDate", "Date '${0}' is invalid. Use yyyy-MM-dd."), + FAIL_DATE_RANGE("command.fail.invalidDateRange", "Start date ${0} can not be later than end date ${1}."), USER_NOT_LINKED("command.fail.missingLink", "Cmd FAIL - Users not linked", "User is not linked to your account and you don't have permission to remove other user's accounts."), FAIL_WEB_USER_EXISTS("command.fail.webUserExists", "Cmd FAIL - WebUser exists", "§cUser already exists!"), @@ -128,6 +131,8 @@ public enum CommandLang implements Lang { DB_WRITE("command.database.write", "Cmd db - write", "Writing to ${0}.."), DB_REMOVAL("command.database.removal", "Cmd db - removal", "Removing Plan-data from ${0}.."), DB_REMOVAL_PLAYER("command.database.playerRemoval", "Cmd db - removal player", "Removing data of ${0} from ${1}.."), + DB_REMOVAL_PLAYERS("command.database.playersRemoval", "Removing players in the selected registration date range from ${0}.."), + DB_REMOVAL_PLAYERS_SUCCESS("command.database.playersRemovalSuccess", "> §aRemoved ${0} players."), DB_UNINSTALLED("command.database.serverUninstalled", "Cmd db - server uninstalled", "§aIf the server is still installed, it will automatically set itself as installed in the database."), UNREGISTER("command.database.unregister", "Cmd unregister - unregistering", "Unregistering '${0}'.."), @@ -183,4 +188,4 @@ public String getIdentifier() { public String getDefault() { return defaultValue; } -} \ No newline at end of file +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/DeepHelpLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/DeepHelpLang.java index 4381ebb00d..263fd4ec4b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/DeepHelpLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/DeepHelpLang.java @@ -44,6 +44,7 @@ public enum DeepHelpLang implements Lang { DB_HOTSWAP("command.help.dbHotswap.inDepth", "In Depth Help - /plan db hotswap", "Reloads the plugin with the other database and changes the config to match."), DB_CLEAR("command.help.dbClear.inDepth", "In Depth Help - /plan db clear", "Clears all Plan tables, removing all Plan-data in the process."), DB_REMOVE("command.help.dbRemove.inDepth", "In Depth Help - /plan db remove", "Removes all data linked to a player from the Current database."), + DB_REMOVE_REGISTERED("command.help.dbRemoveRegistered.inDepth", "In Depth Help - /plan db remove_registered", "Removes all data of players whose first join date is between the two yyyy-MM-dd dates (inclusive)."), DB_UNINSTALLED("command.help.dbUninstalled.inDepth", "In Depth Help - /plan db uninstalled", "Marks a server in Plan database as uninstalled so that it will not show up in server queries."), EXPORT("command.help.export.inDepth", "In Depth Help - /plan export", "Performs an export to export location defined in the config."), IMPORT("command.help.import.inDepth", "In Depth Help - /plan import", "Performs an import to load data into the database."), diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java index 8a732167f2..4c783264ff 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HelpLang.java @@ -30,6 +30,8 @@ public enum HelpLang implements Lang { ARG_FEATURE("command.argument.feature.name", "CMD Arg Name - feature", "feature"), ARG_SUBCOMMAND("command.argument.subcommand.name", "CMD Arg Name - subcommand", "subcommand"), ARG_BACKUP_FILE("command.argument.backupFile.name", "CMD Arg Name - backup-file", "backup-file"), + ARG_AFTER_DATE("command.argument.afterDate.name", "CMD Arg Name - after date", "after-date"), + ARG_BEFORE_DATE("command.argument.beforeDate.name", "CMD Arg Name - before date", "before-date"), ARG_EXPORT_KIND("command.argument.exportKind", "CMD Arg Name - export kind", "export kind"), ARG_IMPORT_KIND("command.argument.importKind", "CMD Arg Name - import kind", "import kind"), DESC_ARG_SERVER_IDENTIFIER("command.argument.server.description", "CMD Arg - server identifier", "Name, ID or UUID of a server"), @@ -41,6 +43,8 @@ public enum HelpLang implements Lang { DESC_ARG_FEATURE("command.argument.feature.description", "CMD Arg - feature", "Name of the feature to disable: ${0}"), DESC_ARG_SUBCOMMAND("command.argument.subcommand.description", "CMD Arg - subcommand", "Use the command without subcommand to see help."), DESC_ARG_BACKUP_FILE("command.argument.backupFile.description", "CMD Arg - backup-file", "Name of the backup file (case sensitive)"), + DESC_ARG_AFTER_DATE("command.argument.afterDate.description", "CMD Arg - after date", "First registration date to remove, formatted yyyy-MM-dd"), + DESC_ARG_BEFORE_DATE("command.argument.beforeDate.description", "CMD Arg - before date", "Last registration date to remove, formatted yyyy-MM-dd"), DESC_ARG_DB_BACKUP("command.argument.dbBackup.description", "CMD Arg - db type backup", "Type of the database to backup. Current database is used if not specified."), DESC_ARG_DB_RESTORE("command.argument.dbRestore.description", "CMD Arg - db type restore", "Type of the database to restore to. Current database is used if not specified."), DESC_ARG_DB_MOVE_FROM("command.argument.dbTypeMoveFrom.description", "CMD Arg - db type move from", "Type of the database to move data from."), @@ -71,6 +75,7 @@ public enum HelpLang implements Lang { DB_HOTSWAP("command.help.dbHotswap.description", "Command Help - /plan db hotswap", "Change Database quickly"), DB_CLEAR("command.help.dbClear.description", "Command Help - /plan db clear", "Remove ALL Plan data from a database"), DB_REMOVE("command.help.dbRemove.description", "Command Help - /plan db remove", "Remove player's data from Current database"), + DB_REMOVE_REGISTERED("command.help.dbRemoveRegistered.description", "Command Help - /plan db remove_registered", "Remove players registered in a date range"), DB_UNINSTALLED("command.help.dbUninstalled.description", "Command Help - /plan db uninstalled", "Set a server as uninstalled in the database."), EXPORT("command.help.export.description", "Command Help - /plan export", "Export html or json files manually"), IMPORT("command.help.import.description", "Command Help - /plan import", "Import data"), @@ -103,4 +108,4 @@ public String getIdentifier() { public String getDefault() { return defaultValue; } -} \ No newline at end of file +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/BaseUserQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/BaseUserQueries.java index f67b0bd9c6..8ec2bf1a14 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/BaseUserQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/BaseUserQueries.java @@ -97,6 +97,15 @@ public static Query> userIdsOfRegisteredBetween(long after, long be return db -> db.querySet(sql, RowExtractors.getInt(UsersTable.ID), after, before); } + public static Query> playerUUIDsOfRegisteredBetween(long after, long before) { + String sql = SELECT + DISTINCT + UsersTable.USER_UUID + + FROM + UsersTable.TABLE_NAME + + WHERE + UsersTable.REGISTERED + ">=?" + + AND + UsersTable.REGISTERED + "<=?"; + + return db -> db.querySet(sql, RowExtractors.getUUID(UsersTable.USER_UUID), after, before); + } + public static Query> minimumRegisterDate() { String sql = SELECT + min(UsersTable.REGISTERED) + " as min" + FROM + UsersTable.TABLE_NAME; @@ -128,4 +137,4 @@ public static Query> fetchBaseUsers(int afterId, int limit) { .toString(); return db -> db.queryList(sql, BaseUserQueries::extractBaseUser); } -} \ No newline at end of file +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemovePlayersRegisteredBetweenTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemovePlayersRegisteredBetweenTransaction.java new file mode 100644 index 0000000000..b96acb4f60 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemovePlayersRegisteredBetweenTransaction.java @@ -0,0 +1,53 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.storage.database.transactions.commands; + +import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries; +import com.djrapitops.plan.storage.database.transactions.Transaction; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Transaction for removing all players whose first join date is in a given range. + */ +public class RemovePlayersRegisteredBetweenTransaction extends Transaction { + + private final long after; + private final long before; + private final Set removedPlayerUUIDs = new LinkedHashSet<>(); + + public RemovePlayersRegisteredBetweenTransaction(long after, long before) { + if (after > before) throw new IllegalArgumentException("after can not be later than before"); + this.after = after; + this.before = before; + } + + @Override + protected void performOperations() { + removedPlayerUUIDs.addAll(query(BaseUserQueries.playerUUIDsOfRegisteredBetween(after, before))); + for (UUID playerUUID : removedPlayerUUIDs) { + executeOther(new RemovePlayerTransaction(playerUUID)); + } + } + + public Set getRemovedPlayerUUIDs() { + return Collections.unmodifiableSet(removedPlayerUUIDs); + } +} diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml index fecc604032..8ff06298ff 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml @@ -4,6 +4,12 @@ command: backupFile: description: "Name of the backup file (case sensitive)" name: "backup-file" + afterDate: + description: "First registration date to remove, formatted yyyy-MM-dd" + name: "after-date" + beforeDate: + description: "Last registration date to remove, formatted yyyy-MM-dd" + name: "before-date" code: description: "Code used to finalize registration." name: "${code}" @@ -51,6 +57,7 @@ command: dbMerge: "You are about to merge data in ${0} to data in ${1}" dbOverwrite: "You are about to overwrite data in Plan ${0} with data in ${1}" dbRemovePlayer: "You are about to remove data of ${0} from ${1}" + dbRemovePlayersBetween: "You are about to remove players registered from ${0} through ${1} from ${2}" deny: "Cancel" expired: "Confirmation expired, use the command again" unregister: "You are about to unregister '${0}' linked to ${1}" @@ -97,6 +104,8 @@ command: start: "> §2Processing data.." success: "> §aSuccess!" playerRemoval: "Removing data of ${0} from ${1}.." + playersRemoval: "Removing players in the selected registration date range from ${0}.." + playersRemovalSuccess: "> §aRemoved ${0} players." removal: "Removing Plan-data from ${0}.." serverUninstalled: "§aIf the server is still installed, it will automatically set itself as installed in the database." unregister: "Unregistering '${0}'.." @@ -105,6 +114,8 @@ command: fail: emptyString: "The search string can not be empty" invalidArguments: "Accepts following as ${0}: ${1}" + invalidDate: "Date '${0}' is invalid. Use yyyy-MM-dd." + invalidDateRange: "Start date ${0} can not be later than end date ${1}." invalidUsername: "§cUser does not have an UUID." missingArguments: "§cArguments required (${0}) ${1}" missingFeature: "§eDefine a feature to disable! (currently supports ${0})" @@ -169,6 +180,9 @@ command: dbRemove: description: "Remove player's data from Current database" inDepth: "Removes all data linked to a player from the Current database." + dbRemoveRegistered: + description: "Remove players registered in a date range" + inDepth: "Removes all data of players whose first join date is between the two yyyy-MM-dd dates (inclusive)." dbRestore: description: "Restore data from a file to a database" inDepth: "Uses SQLite backup file and overwrites contents of the target database." diff --git a/Plan/common/src/test/java/com/djrapitops/plan/commands/PlanCommandTest.java b/Plan/common/src/test/java/com/djrapitops/plan/commands/PlanCommandTest.java index 9f011d54a0..b83b78afe2 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/commands/PlanCommandTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/commands/PlanCommandTest.java @@ -102,6 +102,7 @@ void buildingHasNoBuilderErrors(PlanCommand command) { "db backup SQLite", "db clear SQLite", "db remove Test", + "db remove_registered 2026-01-01 2026-01-02", "db uninstalled 1", "db removejoinaddresses 1", }) @@ -215,4 +216,4 @@ void backupCommandCreatesBackup(PlanFiles files) throws IOException { assertTrue(foundBackupFile.contains("backup")); } } -} \ No newline at end of file +} diff --git a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTest.java b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTest.java index 344e948c3d..da071162cd 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTest.java @@ -41,6 +41,7 @@ import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction; import com.djrapitops.plan.storage.database.transactions.Transaction; import com.djrapitops.plan.storage.database.transactions.commands.RemovePlayerTransaction; +import com.djrapitops.plan.storage.database.transactions.commands.RemovePlayersRegisteredBetweenTransaction; import com.djrapitops.plan.storage.database.transactions.commands.RemoveServerTransaction; import com.djrapitops.plan.storage.database.transactions.events.*; import com.djrapitops.plan.storage.database.transactions.init.CreateIndexTransaction; @@ -123,6 +124,21 @@ default void testRemovalSingleUser() { assertMapIsEmpty(db(), SessionQueries.fetchSessionsOfPlayer(playerUUID)); } + @Test + default void removePlayersRegisteredBetweenDates() { + db().executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> 1000L, TestConstants.PLAYER_ONE_NAME)); + db().executeTransaction(new PlayerRegisterTransaction(player2UUID, () -> 2000L, TestConstants.PLAYER_TWO_NAME)); + db().executeTransaction(new PlayerRegisterTransaction(player3UUID, () -> 3000L, TestConstants.PLAYER_THREE_NAME)); + + RemovePlayersRegisteredBetweenTransaction transaction = new RemovePlayersRegisteredBetweenTransaction(1000L, 2000L); + db().executeTransaction(transaction).join(); + + assertEquals(Set.of(playerUUID, player2UUID), transaction.getRemovedPlayerUUIDs()); + assertFalse(db().query(PlayerFetchQueries.isPlayerRegistered(playerUUID))); + assertFalse(db().query(PlayerFetchQueries.isPlayerRegistered(player2UUID))); + assertTrue(db().query(PlayerFetchQueries.isPlayerRegistered(player3UUID))); + } + @Test default void serverDataIsRemoved() { saveUserTwo();