Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ private Subcommand databaseCommand() {
.subcommand(hotswapCommand())
.subcommand(clearCommand())
.subcommand(removeCommand())
.subcommand(removeRegisteredBetweenCommand())
.subcommand(uninstalledCommand())
.subcommand(removeJoinAddressesCommand())
.subcommand(onlineUuidMigration())
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -576,4 +589,4 @@ private Subcommand groups() {
.onCommand(registrationCommands::onListWebGroups)
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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, "<yyyy-MM-dd> <yyyy-MM-dd>")));
@Untrusted String beforeDate = arguments.get(1)
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ARGS, 2, "<yyyy-MM-dd> <yyyy-MM-dd>")));

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<UUID> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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!"),
Expand Down Expand Up @@ -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}'.."),

Expand Down Expand Up @@ -183,4 +188,4 @@ public String getIdentifier() {
public String getDefault() {
return defaultValue;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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."),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -103,4 +108,4 @@ public String getIdentifier() {
public String getDefault() {
return defaultValue;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ public static Query<Set<Integer>> userIdsOfRegisteredBetween(long after, long be
return db -> db.querySet(sql, RowExtractors.getInt(UsersTable.ID), after, before);
}

public static Query<Set<UUID>> 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<Optional<Long>> minimumRegisterDate() {
String sql = SELECT + min(UsersTable.REGISTERED) + " as min" +
FROM + UsersTable.TABLE_NAME;
Expand Down Expand Up @@ -128,4 +137,4 @@ public static Query<List<BaseUser>> fetchBaseUsers(int afterId, int limit) {
.toString();
return db -> db.queryList(sql, BaseUserQueries::extractBaseUser);
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<UUID> 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<UUID> getRemovedPlayerUUIDs() {
return Collections.unmodifiableSet(removedPlayerUUIDs);
}
}
14 changes: 14 additions & 0 deletions Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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}'.."
Expand All @@ -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})"
Expand Down Expand Up @@ -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."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
})
Expand Down Expand Up @@ -215,4 +216,4 @@ void backupCommandCreatesBackup(PlanFiles files) throws IOException {
assertTrue(foundBackupFile.contains("backup"));
}
}
}
}
Loading