diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/FilterDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/FilterDto.java new file mode 100644 index 000000000..bbfe971e5 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/FilterDto.java @@ -0,0 +1,73 @@ +/* + * 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.delivery.domain.datatransfer; + +import com.djrapitops.plan.storage.database.queries.filter.Filter; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +/** + * Represents a query filter. + * + * @see Filter + * @see com.djrapitops.plan.modules.FiltersModule + */ +public class FilterDto implements Comparable { + private final String kind; + private final Map options; + private final String[] expectedParameters; + + public FilterDto(String kind, Filter filter) { + this.kind = kind; + this.options = filter.getOptions(); + this.expectedParameters = filter.getExpectedParameters(); + } + + public String getKind() { + return kind; + } + + public Map getOptions() { + return options; + } + + public String[] getExpectedParameters() { + return expectedParameters; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FilterDto that = (FilterDto) o; + return Objects.equals(kind, that.kind) && Objects.equals(options, that.options) && Arrays.equals(expectedParameters, that.expectedParameters); + } + + @Override + public int hashCode() { + int result = Objects.hash(kind, options); + result = 31 * result + Arrays.hashCode(expectedParameters); + return result; + } + + @Override + public int compareTo(FilterDto o) { + return String.CASE_INSENSITIVE_ORDER.compare(this.kind, o.kind); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/SpecifiedFilterInformation.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputFilterDto.java similarity index 78% rename from Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/SpecifiedFilterInformation.java rename to Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputFilterDto.java index 0555c289b..013b5ec62 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/SpecifiedFilterInformation.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputFilterDto.java @@ -14,8 +14,9 @@ * 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.queries.filter; +package com.djrapitops.plan.delivery.domain.datatransfer; +import com.djrapitops.plan.storage.database.queries.filter.Filter; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -27,18 +28,18 @@ import java.util.*; * * @author AuroraLS3 */ -public class SpecifiedFilterInformation { +public class InputFilterDto { private final String kind; private final Map parameters; - public SpecifiedFilterInformation(String kind, Map parameters) { + public InputFilterDto(String kind, Map parameters) { this.kind = kind; this.parameters = parameters; } - public static List parse(String json) throws IOException { - return new Gson().getAdapter(new TypeToken>() {}).fromJson(json); + public static List parse(String json, Gson gson) throws IOException { + return gson.getAdapter(new TypeToken>() {}).fromJson(json); } public String getKind() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputQueryDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputQueryDto.java new file mode 100644 index 000000000..243a32101 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/InputQueryDto.java @@ -0,0 +1,60 @@ +/* + * 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.delivery.domain.datatransfer; + +import java.util.List; +import java.util.Objects; + +public class InputQueryDto { + + public final List filters; + private final ViewDto view; + + public InputQueryDto(ViewDto view, List filters) { + this.view = view; + this.filters = filters; + } + + public ViewDto getView() { + return view; + } + + public List getFilters() { + return filters; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InputQueryDto that = (InputQueryDto) o; + return Objects.equals(getView(), that.getView()) && Objects.equals(getFilters(), that.getFilters()); + } + + @Override + public int hashCode() { + return Objects.hash(getView(), getFilters()); + } + + @Override + public String toString() { + return "InputQueryDto{" + + "view=" + view + + ", filters=" + filters + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ServerDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ServerDto.java new file mode 100644 index 000000000..0c5944051 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ServerDto.java @@ -0,0 +1,60 @@ +/* + * 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.delivery.domain.datatransfer; + +import com.djrapitops.plan.identification.Server; + +/** + * Represents outgoing server information json. + */ +public class ServerDto { + + private final String serverUUID; + private final String serverName; + private final boolean proxy; + + public ServerDto(String serverUUID, String serverName, boolean proxy) { + this.serverUUID = serverUUID; + this.serverName = serverName; + this.proxy = proxy; + } + + public static ServerDto fromServer(Server server) { + return new ServerDto(server.getUuid().toString(), server.getIdentifiableName(), server.isProxy()); + } + + public String getServerUUID() { + return serverUUID; + } + + public String getServerName() { + return serverName; + } + + public boolean isProxy() { + return proxy; + } + + @Override + public String toString() { + return "ServerDto{" + + "serverUUID='" + serverUUID + '\'' + + ", serverName='" + serverName + '\'' + + ", proxy=" + proxy + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ViewDto.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ViewDto.java new file mode 100644 index 000000000..12d899923 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ViewDto.java @@ -0,0 +1,85 @@ +/* + * 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.delivery.domain.datatransfer; + +import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.identification.ServerUUID; +import org.apache.commons.lang3.StringUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Represents query page view that the user wants to see data for. + */ +public class ViewDto { + private static final String DATE_PATTERN = "dd/MM/yyyy kk:mm"; + + private final String afterDate; + private final String afterTime; + private final String beforeDate; + private final String beforeTime; + private final List servers; + + public ViewDto(Formatters formatters, List servers) { + this.servers = servers; + long now = System.currentTimeMillis(); + long monthAgo = now - TimeUnit.DAYS.toMillis(30); + + Formatter formatter = formatters.javascriptDateFormatterLong(); + String[] after = StringUtils.split(formatter.apply(monthAgo), " "); + String[] before = StringUtils.split(formatter.apply(now), " "); + + this.afterDate = after[0]; + this.afterTime = after[1]; + this.beforeDate = before[0]; + this.beforeTime = before[1]; + } + + public long getAfterEpochMs() throws ParseException { + return new SimpleDateFormat(DATE_PATTERN).parse(afterDate + " " + afterTime).getTime(); + } + + public long getBeforeEpochMs() throws ParseException { + return new SimpleDateFormat(DATE_PATTERN).parse(beforeDate + " " + beforeTime).getTime(); + } + + public List getServerUUIDs() { + return servers.stream() + .map(ServerDto::getServerUUID) + .map(ServerUUID::fromString) + .collect(Collectors.toList()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViewDto viewDto = (ViewDto) o; + return Objects.equals(afterDate, viewDto.afterDate) && Objects.equals(afterTime, viewDto.afterTime) && Objects.equals(beforeDate, viewDto.beforeDate) && Objects.equals(beforeTime, viewDto.beforeTime) && Objects.equals(servers, viewDto.servers); + } + + @Override + public int hashCode() { + return Objects.hash(afterDate, afterTime, beforeDate, beforeTime, servers); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/package-info.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/package-info.java new file mode 100644 index 000000000..ad3bc05e9 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/package-info.java @@ -0,0 +1,4 @@ +/** + * Data transfer objects or DTOs, which represent the outgoing or incoming serialized json. + */ +package com.djrapitops.plan.delivery.domain.datatransfer; \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java index 71d2cbae8..e368687f6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.delivery.rendering.json; import com.djrapitops.plan.delivery.domain.DateObj; +import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto; import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator; import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; import com.djrapitops.plan.delivery.domain.mutators.TPSMutator; @@ -266,16 +267,10 @@ public class JSONFactory { return tableEntries; } - public Map listServers() { + public Map> listServers() { Collection servers = dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformationCollection()); - return Maps.builder(String.class, Object.class) - .put("servers", servers.stream() - .map(server -> Maps.builder(String.class, Object.class) - .put("serverUUID", server.getUuid().toString()) - .put("serverName", server.getIdentifiableName()) - .put("proxy", server.isProxy()) - .build()) - .collect(Collectors.toList())) - .build(); + return Collections.singletonMap("servers", servers.stream() + .map(ServerDto::fromServer) + .collect(Collectors.toList())); } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/WebAssetVersionCheckTask.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/WebAssetVersionCheckTask.java index ec4c3c401..c52c34110 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/WebAssetVersionCheckTask.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/WebAssetVersionCheckTask.java @@ -89,6 +89,7 @@ public class WebAssetVersionCheckTask extends TaskSystem.Task { if (!outdated.isEmpty()) { logger.warn("You have customized files which are out of date due to recent updates!"); logger.warn("Plan may not work properly until these files are updated to include the new modifications."); + logger.warn("See https://github.com/plan-player-analytics/Plan/commits/html to compare changes"); } for (AssetInfo asset : outdated) { logger.warn(String.format("- %s was modified %s, but the plugin contains a version from %s", diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestBodyConverter.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestBodyConverter.java index ac6329b95..9b0226602 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestBodyConverter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/RequestBodyConverter.java @@ -18,26 +18,38 @@ package com.djrapitops.plan.delivery.webserver; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.URIQuery; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.nio.charset.StandardCharsets; public class RequestBodyConverter { - private RequestBodyConverter() { - /* Static utility class */ - } - - /** - * Get the body of a request as an url-encoded form. - * - * @return {@link URIQuery}. - */ - public static URIQuery formBody(Request request) { - if ( - "POST".equalsIgnoreCase(request.getMethod()) && - "application/x-www-form-urlencoded".equalsIgnoreCase(request.getHeader("Content-type").orElse("")) - ) { - return new URIQuery(new String(request.getRequestBody())); - } else { - return new URIQuery(""); + private RequestBodyConverter() { + /* Static utility class */ + } + + /** + * Get the body of a request as an url-encoded form. + * + * @return {@link URIQuery}. + */ + public static URIQuery formBody(Request request) { + if ( + "POST".equalsIgnoreCase(request.getMethod()) && + "application/x-www-form-urlencoded".equalsIgnoreCase(request.getHeader("Content-type").orElse("")) + ) { + return new URIQuery(new String(request.getRequestBody(), StandardCharsets.UTF_8)); + } else { + return new URIQuery(""); + } + } + + public static T bodyJson(Request request, Gson gson, Class ofType) { + return gson.fromJson(new String(request.getRequestBody(), StandardCharsets.UTF_8), ofType); + } + + public static T bodyJson(Request request, Gson gson, TypeToken ofType) { + return gson.fromJson(new String(request.getRequestBody(), StandardCharsets.UTF_8), ofType.getType()); } - } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/FiltersJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/FiltersJSONResolver.java index 9abde9d0a..368edd402 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/FiltersJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/FiltersJSONResolver.java @@ -17,8 +17,10 @@ package com.djrapitops.plan.delivery.webserver.resolver.json; import com.djrapitops.plan.delivery.domain.DateObj; -import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.domain.datatransfer.FilterDto; +import com.djrapitops.plan.delivery.domain.datatransfer.ViewDto; import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.delivery.rendering.json.JSONFactory; import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs; import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraph; import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point; @@ -36,7 +38,6 @@ import com.djrapitops.plan.storage.database.queries.objects.TPSQueries; import com.djrapitops.plan.utilities.java.Lists; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; -import org.apache.commons.lang3.StringUtils; import javax.inject.Inject; import javax.inject.Singleton; @@ -50,6 +51,7 @@ public class FiltersJSONResolver implements Resolver { private final ServerInfo serverInfo; private final DBSystem dbSystem; private final QueryFilters filters; + private final JSONFactory jsonFactory; private final Graphs graphs; private final Formatters formatters; private final ErrorLogger errorLogger; @@ -59,6 +61,7 @@ public class FiltersJSONResolver implements Resolver { ServerInfo serverInfo, DBSystem dbSystem, QueryFilters filters, + JSONFactory jsonFactory, Graphs graphs, Formatters formatters, ErrorLogger errorLogger @@ -66,6 +69,7 @@ public class FiltersJSONResolver implements Resolver { this.serverInfo = serverInfo; this.dbSystem = dbSystem; this.filters = filters; + this.jsonFactory = jsonFactory; this.graphs = graphs; this.formatters = formatters; this.errorLogger = errorLogger; @@ -85,9 +89,9 @@ public class FiltersJSONResolver implements Resolver { private Response getResponse() { return Response.builder() .setMimeType(MimeType.JSON) - .setJSONContent(new FilterResponseJSON( + .setJSONContent(new FilterResponseDto( filters.getFilters(), - new ViewJSON(formatters), + new ViewDto(formatters, jsonFactory.listServers().get("servers")), fetchViewGraphPoints() )).build(); } @@ -111,17 +115,17 @@ public class FiltersJSONResolver implements Resolver { /** * JSON serialization class. */ - class FilterResponseJSON { - final List filters; - final ViewJSON view; + class FilterResponseDto { + final List filters; + final ViewDto view; final List viewPoints; - public FilterResponseJSON(Map filtersByKind, ViewJSON view, List viewPoints) { + public FilterResponseDto(Map filtersByKind, ViewDto view, List viewPoints) { this.viewPoints = viewPoints; this.filters = new ArrayList<>(); for (Map.Entry entry : filtersByKind.entrySet()) { try { - filters.add(new FilterJSON(entry.getKey(), entry.getValue())); + filters.add(new FilterDto(entry.getKey(), entry.getValue())); } catch (Exception e) { errorLogger.error(e, ErrorContext.builder() .whatToDo("Report this, filter '" + entry.getKey() + "' has implementation error.") @@ -133,63 +137,4 @@ public class FiltersJSONResolver implements Resolver { this.view = view; } } - - /** - * JSON serialization class. - */ - static class FilterJSON implements Comparable { - final String kind; - final Map options; - final String[] expectedParameters; - - public FilterJSON(String kind, Filter filter) { - this.kind = kind; - this.options = filter.getOptions(); - this.expectedParameters = filter.getExpectedParameters(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FilterJSON that = (FilterJSON) o; - return Objects.equals(kind, that.kind) && Objects.equals(options, that.options) && Arrays.equals(expectedParameters, that.expectedParameters); - } - - @Override - public int hashCode() { - int result = Objects.hash(kind, options); - result = 31 * result + Arrays.hashCode(expectedParameters); - return result; - } - - @Override - public int compareTo(FilterJSON o) { - return String.CASE_INSENSITIVE_ORDER.compare(this.kind, o.kind); - } - } - - /** - * JSON serialization class. - */ - static class ViewJSON { - final String afterDate; - final String afterTime; - final String beforeDate; - final String beforeTime; - - public ViewJSON(Formatters formatters) { - long now = System.currentTimeMillis(); - long monthAgo = now - TimeUnit.DAYS.toMillis(30); - - Formatter formatter = formatters.javascriptDateFormatterLong(); - String[] after = StringUtils.split(formatter.apply(monthAgo), " "); - String[] before = StringUtils.split(formatter.apply(now), " "); - - this.afterDate = after[0]; - this.afterTime = after[1]; - this.beforeDate = before[0]; - this.beforeTime = before[1]; - } - } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkPerformanceJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkPerformanceJSONResolver.java index d1f1b032e..f8dbe3714 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkPerformanceJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkPerformanceJSONResolver.java @@ -57,13 +57,15 @@ public class NetworkPerformanceJSONResolver implements Resolver { private final Formatter timeAmount; private final Formatter percentage; private final Formatter byteSize; + private final Gson gson; @Inject public NetworkPerformanceJSONResolver( PlanConfig config, Locale locale, DBSystem dbSystem, - Formatters formatters + Formatters formatters, + Gson gson ) { this.config = config; this.locale = locale; @@ -73,6 +75,7 @@ public class NetworkPerformanceJSONResolver implements Resolver { percentage = formatters.percentage(); timeAmount = formatters.timeAmount(); byteSize = formatters.byteSize(); + this.gson = gson; } @Override @@ -93,7 +96,7 @@ public class NetworkPerformanceJSONResolver implements Resolver { } private List getUUIDList(String jsonString) { - return new Gson().fromJson(jsonString, new TypeToken>() {}.getType()); + return gson.fromJson(jsonString, new TypeToken>() {}.getType()); } public Map createJSONAsMap(Collection serverUUIDs) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java index 3ca615b44..9c6cf38fd 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/QueryJSONResolver.java @@ -17,6 +17,9 @@ package com.djrapitops.plan.delivery.webserver.resolver.json; import com.djrapitops.plan.delivery.domain.DateMap; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.delivery.domain.datatransfer.InputQueryDto; +import com.djrapitops.plan.delivery.domain.datatransfer.ViewDto; import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.rendering.json.PlayersTableJSONCreator; @@ -27,9 +30,11 @@ import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.WebUser; +import com.djrapitops.plan.delivery.webserver.RequestBodyConverter; import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionQueryResultTableDataQuery; import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.DisplaySettings; import com.djrapitops.plan.settings.config.paths.TimeSettings; @@ -39,7 +44,6 @@ import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries; import com.djrapitops.plan.storage.database.queries.filter.Filter; import com.djrapitops.plan.storage.database.queries.filter.QueryFilters; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.GeoInfoQueries; import com.djrapitops.plan.storage.database.queries.objects.SessionQueries; import com.djrapitops.plan.storage.database.queries.objects.playertable.QueryTablePlayersQuery; @@ -52,7 +56,6 @@ import javax.inject.Singleton; import java.io.IOException; import java.net.URLDecoder; import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.*; @Singleton @@ -67,6 +70,7 @@ public class QueryJSONResolver implements Resolver { private final GraphJSONCreator graphJSONCreator; private final Locale locale; private final Formatters formatters; + private final Gson gson; @Inject public QueryJSONResolver( @@ -76,7 +80,8 @@ public class QueryJSONResolver implements Resolver { ServerInfo serverInfo, JSONStorage jsonStorage, GraphJSONCreator graphJSONCreator, Locale locale, - Formatters formatters + Formatters formatters, + Gson gson ) { this.filters = filters; this.config = config; @@ -86,6 +91,7 @@ public class QueryJSONResolver implements Resolver { this.graphJSONCreator = graphJSONCreator; this.locale = locale; this.formatters = formatters; + this.gson = gson; } @Override @@ -103,17 +109,33 @@ public class QueryJSONResolver implements Resolver { Optional cachedResult = checkForCachedResult(request); if (cachedResult.isPresent()) return cachedResult.get(); - String q = request.getQuery().get("q").orElseThrow(() -> new BadRequestException("'q' parameter not set (expecting json array)")); - String view = request.getQuery().get("view").orElseThrow(() -> new BadRequestException("'view' parameter not set (expecting json object {afterDate, afterTime, beforeDate, beforeTime})")); + InputQueryDto inputQuery = parseInputQuery(request); + List queries = inputQuery.getFilters(); + Filter.Result result = filters.apply(queries); + List resultPath = result.getInverseResultPath(); + Collections.reverse(resultPath); + + return buildAndStoreResponse(inputQuery.getView(), result, resultPath); + } + + private InputQueryDto parseInputQuery(Request request) { + if (request.getRequestBody().length == 0) { + return parseInputQueryFromQueryParams(request); + } else { + return RequestBodyConverter.bodyJson(request, gson, InputQueryDto.class); + } + } + + private InputQueryDto parseInputQueryFromQueryParams(Request request) { + String q = request.getQuery().get("q").orElseThrow(() -> new BadRequestException("'q' parameter not set (expecting json array)")); try { String query = URLDecoder.decode(q, "UTF-8"); - List queries = SpecifiedFilterInformation.parse(query); - Filter.Result result = filters.apply(queries); - List resultPath = result.getInverseResultPath(); - Collections.reverse(resultPath); - - return buildAndStoreResponse(view, result, resultPath); + List queryFilters = InputFilterDto.parse(query, gson); + ViewDto view = request.getQuery().get("view") + .map(viewJson -> gson.fromJson(viewJson, ViewDto.class)) + .orElseThrow(() -> new BadRequestException("'view' parameter not set (expecting json object {afterDate, afterTime, beforeDate, beforeTime})")); + return new InputQueryDto(view, queryFilters); } catch (IOException e) { throw new BadRequestException("Failed to decode json: '" + q + "', " + e.getMessage()); } @@ -132,12 +154,12 @@ public class QueryJSONResolver implements Resolver { } } - private Response buildAndStoreResponse(String view, Filter.Result result, List resultPath) { + private Response buildAndStoreResponse(ViewDto view, Filter.Result result, List resultPath) { try { long timestamp = System.currentTimeMillis(); Map json = Maps.builder(String.class, Object.class) .put("path", resultPath) - .put("view", new Gson().fromJson(view, FiltersJSONResolver.ViewJSON.class)) + .put("view", view) .put("timestamp", timestamp) .build(); if (!result.isEmpty()) { @@ -155,23 +177,22 @@ public class QueryJSONResolver implements Resolver { } } - private Map getDataFor(Set playerUUIDs, String view) throws ParseException { - FiltersJSONResolver.ViewJSON viewJSON = new Gson().fromJson(view, FiltersJSONResolver.ViewJSON.class); - SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy kk:mm"); - long after = dateFormat.parse(viewJSON.afterDate + " " + viewJSON.afterTime).getTime(); - long before = dateFormat.parse(viewJSON.beforeDate + " " + viewJSON.beforeTime).getTime(); + private Map getDataFor(Set playerUUIDs, ViewDto view) throws ParseException { + long after = view.getAfterEpochMs(); + long before = view.getBeforeEpochMs(); + List serverUUIDs = view.getServerUUIDs(); return Maps.builder(String.class, Object.class) - .put("players", getPlayersTableData(playerUUIDs, after, before)) - .put("activity", getActivityGraphData(playerUUIDs, after, before)) + .put("players", getPlayersTableData(playerUUIDs, serverUUIDs, after, before)) + .put("activity", getActivityGraphData(playerUUIDs, serverUUIDs, after, before)) .put("geolocation", getGeolocationData(playerUUIDs)) - .put("sessions", getSessionSummaryData(playerUUIDs, after, before)) + .put("sessions", getSessionSummaryData(playerUUIDs, serverUUIDs, after, before)) .build(); } - private Map getSessionSummaryData(Set playerUUIDs, long after, long before) { + private Map getSessionSummaryData(Set playerUUIDs, List serverUUIDs, long after, long before) { Database database = dbSystem.getDatabase(); - Map summary = database.query(SessionQueries.summaryOfPlayers(playerUUIDs, after, before)); + Map summary = database.query(SessionQueries.summaryOfPlayers(playerUUIDs, serverUUIDs, after, before)); Map formattedSummary = new HashMap<>(); Formatter timeAmount = formatters.timeAmount(); for (Map.Entry entry : summary.entrySet()) { @@ -189,7 +210,7 @@ public class QueryJSONResolver implements Resolver { ); } - private Map getActivityGraphData(Set playerUUIDs, long after, long before) { + private Map getActivityGraphData(Set playerUUIDs, List serverUUIDs, long after, long before) { Database database = dbSystem.getDatabase(); Long threshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD); @@ -198,16 +219,16 @@ public class QueryJSONResolver implements Resolver { DateMap> activityData = new DateMap<>(); for (long time = before; time >= stopDate; time -= TimeAmount.WEEK.toMillis(1L)) { - activityData.put(time, database.query(NetworkActivityIndexQueries.fetchActivityIndexGroupingsOn(time, threshold, playerUUIDs))); + activityData.put(time, database.query(NetworkActivityIndexQueries.fetchActivityIndexGroupingsOn(time, threshold, playerUUIDs, serverUUIDs))); } return graphJSONCreator.createActivityGraphJSON(activityData); } - private Map getPlayersTableData(Set playerUUIDs, long after, long before) { + private Map getPlayersTableData(Set playerUUIDs, List serverUUIDs, long after, long before) { Database database = dbSystem.getDatabase(); return new PlayersTableJSONCreator( - database.query(new QueryTablePlayersQuery(playerUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))), + database.query(new QueryTablePlayersQuery(playerUUIDs, serverUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))), database.query(new ExtensionQueryResultTableDataQuery(serverInfo.getServerUUID(), playerUUIDs)), config.get(DisplaySettings.OPEN_PLAYER_LINKS_IN_NEW_TAB), formatters, locale diff --git a/Plan/common/src/main/java/com/djrapitops/plan/modules/FiltersModule.java b/Plan/common/src/main/java/com/djrapitops/plan/modules/FiltersModule.java index b8cdba19d..3fec1d4b0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/modules/FiltersModule.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/modules/FiltersModule.java @@ -57,4 +57,8 @@ public interface FiltersModule { @IntoSet Filter filter8(PluginBooleanGroupFilter filter); + @Binds + @IntoSet + Filter filter9(PlayedOnServerFilter filter); + } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/modules/SystemObjectProvidingModule.java b/Plan/common/src/main/java/com/djrapitops/plan/modules/SystemObjectProvidingModule.java index 6eb426864..c1b500264 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/modules/SystemObjectProvidingModule.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/modules/SystemObjectProvidingModule.java @@ -29,6 +29,7 @@ import com.djrapitops.plan.settings.locale.LocaleSystem; import com.djrapitops.plan.storage.file.JarResource; import com.djrapitops.plan.utilities.logging.ErrorLogger; import com.djrapitops.plan.utilities.logging.PluginErrorLogger; +import com.google.gson.Gson; import dagger.Module; import dagger.Provides; import dagger.multibindings.ElementsIntoSet; @@ -49,6 +50,12 @@ import java.util.function.Predicate; @Module public class SystemObjectProvidingModule { + @Provides + @Singleton + Gson provideGson() { + return new Gson(); + } + @Provides @ElementsIntoSet Set emptyImporterSet() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/NetworkActivityIndexQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/NetworkActivityIndexQueries.java index f4488c0ca..4396f06f0 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/NetworkActivityIndexQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/NetworkActivityIndexQueries.java @@ -17,6 +17,7 @@ package com.djrapitops.plan.storage.database.queries.analysis; import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; @@ -74,6 +75,10 @@ public class NetworkActivityIndexQueries { } public static String selectActivityIndexSQL() { + return selectActivityIndexSQL(Collections.emptyList()); + } + + public static String selectActivityIndexSQL(Collection onServers) { String selectActivePlaytimeSQL = SELECT + "ux." + UsersTable.USER_UUID + ",COALESCE(active_playtime,0) AS active_playtime" + FROM + UsersTable.TABLE_NAME + " ux" + @@ -82,6 +87,7 @@ public class NetworkActivityIndexQueries { FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + ">=?" + AND + SessionsTable.SESSION_START + "<=?" + + (onServers.isEmpty() ? "" : AND + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(onServers, "','") + "')") + GROUP_BY + SessionsTable.USER_UUID + ") sx on sx.uuid=ux.uuid"; @@ -163,8 +169,8 @@ public class NetworkActivityIndexQueries { }; } - public static Query> fetchActivityIndexGroupingsOn(long date, long threshold, Collection playerUUIDs) { - String selectActivityIndex = selectActivityIndexSQL(); + public static Query> fetchActivityIndexGroupingsOn(long date, long threshold, Collection playerUUIDs, List serverUUIDs) { + String selectActivityIndex = selectActivityIndexSQL(serverUUIDs); String selectIndexes = SELECT + "activity_index" + FROM + UsersTable.TABLE_NAME + " u" + diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/Filter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/Filter.java index a193975ac..dfc047f7a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/Filter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/Filter.java @@ -16,6 +16,8 @@ */ package com.djrapitops.plan.storage.database.queries.filter; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; + import java.util.*; /** @@ -40,9 +42,9 @@ public interface Filter { * @return Set of UUIDs this filter applies to * @throws IllegalArgumentException If the arguments are not valid. */ - Set getMatchingUUIDs(SpecifiedFilterInformation query); + Set getMatchingUUIDs(InputFilterDto query); - default Result apply(SpecifiedFilterInformation query) { + default Result apply(InputFilterDto query) { try { return new Result(null, getKind(), getMatchingUUIDs(query)); } catch (CompleteSetException allMatch) { @@ -64,7 +66,7 @@ public interface Filter { this.currentUUIDs = currentUUIDs; } - public Result apply(Filter filter, SpecifiedFilterInformation query) { + public Result apply(Filter filter, InputFilterDto query) { try { Set got = filter.getMatchingUUIDs(query); currentUUIDs.retainAll(got); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/QueryFilters.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/QueryFilters.java index 33b810d58..5c83bede3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/QueryFilters.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/QueryFilters.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.storage.database.queries.filter; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.filters.AllPlayersFilter; @@ -81,25 +82,25 @@ public class QueryFilters { * @return the result object or null if none of the filterQueries could be applied. * @throws BadRequestException If the request kind is not supported or if filter was given bad options. */ - public Filter.Result apply(List filterQueries) { + public Filter.Result apply(List filterQueries) { prepareFilters(); Filter.Result current = null; if (filterQueries.isEmpty()) return allPlayersFilter.apply(null); - for (SpecifiedFilterInformation specifiedFilterInformation : filterQueries) { - current = apply(current, specifiedFilterInformation); + for (InputFilterDto inputFilterDto : filterQueries) { + current = apply(current, inputFilterDto); if (current != null && current.isEmpty()) break; } return current; } - private Filter.Result apply(Filter.Result current, SpecifiedFilterInformation specifiedFilterInformation) { - String kind = specifiedFilterInformation.getKind(); + private Filter.Result apply(Filter.Result current, InputFilterDto inputFilterDto) { + String kind = inputFilterDto.getKind(); Filter filter = getFilter(kind).orElseThrow(() -> new BadRequestException("Filter kind not supported: '" + kind + "'")); - return getResult(current, filter, specifiedFilterInformation); + return getResult(current, filter, inputFilterDto); } - private Filter.Result getResult(Filter.Result current, Filter filter, SpecifiedFilterInformation query) { + private Filter.Result getResult(Filter.Result current, Filter filter, InputFilterDto query) { try { return current == null ? filter.apply(query) : current.apply(filter, query); } catch (IllegalArgumentException badOptions) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/ActivityIndexFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/ActivityIndexFilter.java index ffffd70e5..dad7eeb1d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/ActivityIndexFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/ActivityIndexFilter.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.paths.TimeSettings; @@ -23,7 +24,6 @@ import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries; import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import javax.inject.Inject; import javax.inject.Singleton; @@ -63,7 +63,7 @@ public class ActivityIndexFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { List selected = getSelected(query); String[] options = getOptionsArray(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/AllPlayersFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/AllPlayersFilter.java index 8b1853327..ce5c3d916 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/AllPlayersFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/AllPlayersFilter.java @@ -16,9 +16,9 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.Filter; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.UserIdentifierQueries; import javax.inject.Inject; @@ -52,7 +52,7 @@ public class AllPlayersFilter implements Filter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { return dbSystem.getDatabase().query(UserIdentifierQueries.fetchAllPlayerUUIDs()); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/BannedFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/BannedFilter.java index e7cecb527..5979d3dbc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/BannedFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/BannedFilter.java @@ -16,11 +16,11 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.FilterLang; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; import javax.inject.Inject; @@ -57,7 +57,7 @@ public class BannedFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { List selected = getSelected(query); Set uuids = new HashSet<>(); String[] options = getOptionsArray(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/DateRangeFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/DateRangeFilter.java index 10a1655fd..24597c8dc 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/DateRangeFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/DateRangeFilter.java @@ -16,10 +16,10 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.Filter; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries; import com.djrapitops.plan.utilities.java.Maps; import org.apache.commons.lang3.StringUtils; @@ -61,15 +61,15 @@ public abstract class DateRangeFilter implements Filter { .build(); } - protected long getAfter(SpecifiedFilterInformation query) { + protected long getAfter(InputFilterDto query) { return getTime(query, "afterDate", "afterTime"); } - protected long getBefore(SpecifiedFilterInformation query) { + protected long getBefore(InputFilterDto query) { return getTime(query, "beforeDate", "beforeTime"); } - private long getTime(SpecifiedFilterInformation query, String dateKey, String timeKey) { + private long getTime(InputFilterDto query, String dateKey, String timeKey) { String date = query.get(dateKey).orElseThrow(() -> new BadRequestException("'" + dateKey + "' not specified in parameters for " + getKind())); String time = query.get(timeKey).orElseThrow(() -> new BadRequestException("'" + timeKey + "' not specified in parameters for " + getKind())); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/GeolocationsFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/GeolocationsFilter.java index b548f35c2..1d106ae82 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/GeolocationsFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/GeolocationsFilter.java @@ -16,8 +16,8 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.GeoInfoQueries; import javax.inject.Inject; @@ -47,7 +47,7 @@ public class GeolocationsFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { return dbSystem.getDatabase().query(GeoInfoQueries.uuidsOfPlayersWithGeolocations(getSelected(query))); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/JoinAddressFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/JoinAddressFilter.java index 162b2eac2..43e0d0797 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/JoinAddressFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/JoinAddressFilter.java @@ -16,8 +16,8 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; import javax.inject.Inject; @@ -47,7 +47,7 @@ public class JoinAddressFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { return dbSystem.getDatabase().query(UserInfoQueries.uuidsOfPlayersWithJoinAddresses(getSelected(query))); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/MultiOptionFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/MultiOptionFilter.java index d18bf9369..30033e605 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/MultiOptionFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/MultiOptionFilter.java @@ -16,9 +16,9 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException; import com.djrapitops.plan.storage.database.queries.filter.Filter; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -31,7 +31,7 @@ public abstract class MultiOptionFilter implements Filter { return new String[]{"selected"}; } - protected List getSelected(SpecifiedFilterInformation query) { + protected List getSelected(InputFilterDto query) { String selectedJSON = query.get("selected").orElseThrow(IllegalArgumentException::new); List selected = new Gson().fromJson(selectedJSON, new TypeToken>() {}.getType()); if (selected.isEmpty()) throw new CompleteSetException(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/OperatorsFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/OperatorsFilter.java index 9f98dfe61..bd1d7371b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/OperatorsFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/OperatorsFilter.java @@ -16,11 +16,11 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.lang.FilterLang; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; import javax.inject.Inject; @@ -57,7 +57,7 @@ public class OperatorsFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { List selected = getSelected(query); Set uuids = new HashSet<>(); String[] options = getOptionsArray(); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedBetweenDateRangeFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedBetweenDateRangeFilter.java index 7721a109a..7421d6e8b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedBetweenDateRangeFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedBetweenDateRangeFilter.java @@ -16,14 +16,16 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.storage.database.queries.objects.SessionQueries; +import com.google.gson.Gson; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.Set; -import java.util.UUID; +import java.util.*; @Singleton public class PlayedBetweenDateRangeFilter extends DateRangeFilter { @@ -42,9 +44,18 @@ public class PlayedBetweenDateRangeFilter extends DateRangeFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { long after = getAfter(query); long before = getBefore(query); - return dbSystem.getDatabase().query(SessionQueries.uuidsOfPlayedBetween(after, before)); + List serverNames = getServerNames(query); + List serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames)); + return dbSystem.getDatabase().query(SessionQueries.uuidsOfPlayedBetween(after, before, serverUUIDs)); + } + + private List getServerNames(InputFilterDto query) { + return query.get("servers") + .map(serversList -> new Gson().fromJson(serversList, String[].class)) + .map(Arrays::asList) + .orElseGet(Collections::emptyList); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedOnServerFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedOnServerFilter.java new file mode 100644 index 000000000..d44481f46 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PlayedOnServerFilter.java @@ -0,0 +1,60 @@ +/* + * 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.queries.filter.filters; + +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.*; + +@Singleton +public class PlayedOnServerFilter extends MultiOptionFilter { + + private final DBSystem dbSystem; + + @Inject + public PlayedOnServerFilter(DBSystem dbSystem) { + this.dbSystem = dbSystem; + } + + @Override + public String getKind() { + return "playedOnServer"; + } + + @Override + public Map getOptions() { + return Collections.singletonMap("options", getSelectionOptions()); + } + + private List getSelectionOptions() { + return dbSystem.getDatabase().query(ServerQueries.fetchGameServerNames()); + } + + @Override + public Set getMatchingUUIDs(InputFilterDto query) { + List serverNames = getSelected(query); + List serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames)); + + return dbSystem.getDatabase().query(UserInfoQueries.uuidsOfRegisteredBetween(0, System.currentTimeMillis(), serverUUIDs)); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginBooleanGroupFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginBooleanGroupFilter.java index 930d6cbba..d4c1b3fcf 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginBooleanGroupFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginBooleanGroupFilter.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; @@ -23,7 +24,6 @@ import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerValueTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable; @@ -164,7 +164,7 @@ public class PluginBooleanGroupFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { Map selectedBooleanOptions = new HashMap<>(); for (String selected : getSelected(query)) { String[] optionAndBoolean = StringUtils.split(selected, ":", 2); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginGroupsFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginGroupsFilter.java index a580dbea0..c980aa7c1 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginGroupsFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/PluginGroupsFilter.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; import com.djrapitops.plan.extension.implementation.providers.ProviderIdentifier; import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionUUIDsInGroupQuery; import com.djrapitops.plan.identification.Server; @@ -23,7 +24,6 @@ import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.sql.tables.ExtensionGroupsTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable; @@ -67,7 +67,7 @@ public class PluginGroupsFilter extends MultiOptionFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { return dbSystem.getDatabase().query( new ExtensionUUIDsInGroupQuery(identifier.getPluginName(), identifier.getProviderName(), identifier.getServerUUID(), getSelected(query)) ); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/RegisteredBetweenDateRangeFilter.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/RegisteredBetweenDateRangeFilter.java index 913c2d1e1..15ca9a175 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/RegisteredBetweenDateRangeFilter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/filter/filters/RegisteredBetweenDateRangeFilter.java @@ -16,14 +16,17 @@ */ package com.djrapitops.plan.storage.database.queries.filter.filters; +import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; -import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation; import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries; +import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; +import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries; +import com.google.gson.Gson; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.Set; -import java.util.UUID; +import java.util.*; @Singleton public class RegisteredBetweenDateRangeFilter extends DateRangeFilter { @@ -42,9 +45,21 @@ public class RegisteredBetweenDateRangeFilter extends DateRangeFilter { } @Override - public Set getMatchingUUIDs(SpecifiedFilterInformation query) { + public Set getMatchingUUIDs(InputFilterDto query) { long after = getAfter(query); long before = getBefore(query); - return dbSystem.getDatabase().query(BaseUserQueries.uuidsOfRegisteredBetween(after, before)); + List serverNames = getServerNames(query); + List serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames)); + return dbSystem.getDatabase().query( + serverUUIDs.isEmpty() ? BaseUserQueries.uuidsOfRegisteredBetween(after, before) + : UserInfoQueries.uuidsOfRegisteredBetween(after, before, serverUUIDs) + ); + } + + private List getServerNames(InputFilterDto query) { + return query.get("servers") + .map(serversList -> new Gson().fromJson(serversList, String[].class)) + .map(Arrays::asList) + .orElseGet(Collections::emptyList); } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/ServerQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/ServerQueries.java index 0554f1041..265df05ad 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/ServerQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/ServerQueries.java @@ -30,6 +30,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; +import java.util.stream.Collectors; import static com.djrapitops.plan.storage.database.sql.building.Sql.*; @@ -172,9 +173,27 @@ public class ServerQueries { }; } + public static Query> fetchGameServerNames() { + String sql = Select.from(ServerTable.TABLE_NAME, + ServerTable.SERVER_ID, ServerTable.SERVER_UUID, ServerTable.NAME) + .where(ServerTable.PROXY + "=0") + .toString(); + + return new QueryAllStatement>(sql) { + @Override + public List processResults(ResultSet set) throws SQLException { + List names = new ArrayList<>(); + while (set.next()) { + names.add(Server.getIdentifiableName(set.getString(ServerTable.NAME), set.getInt(ServerTable.SERVER_ID))); + } + return names; + } + }; + } + public static Query> fetchServerNames() { String sql = Select.from(ServerTable.TABLE_NAME, - ServerTable.SERVER_ID, ServerTable.SERVER_UUID, ServerTable.NAME) + ServerTable.SERVER_ID, ServerTable.SERVER_UUID, ServerTable.NAME) .toString(); return new QueryAllStatement>(sql) { @@ -263,4 +282,14 @@ public class ServerQueries { public static Query> fetchServerNamesToUUIDs() { return db -> Maps.reverse(db.query(fetchServerNames())); } + + public static Query> fetchServersMatchingIdentifiers(List serverNames) { + return db -> { + Map nameToUUIDMap = db.query(ServerQueries.fetchServerNamesToUUIDs()); + return serverNames.stream() + .map(nameToUUIDMap::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java index 7cc8a489a..b002ee886 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java @@ -846,11 +846,12 @@ public class SessionQueries { }; } - public static Query> uuidsOfPlayedBetween(long after, long before) { + public static Query> uuidsOfPlayedBetween(long after, long before, List serverUUIDs) { String sql = SELECT + DISTINCT + SessionsTable.USER_UUID + FROM + SessionsTable.TABLE_NAME + WHERE + SessionsTable.SESSION_END + ">=?" + - AND + SessionsTable.SESSION_START + "<=?"; + AND + SessionsTable.SESSION_START + "<=?" + + (serverUUIDs.isEmpty() ? "" : AND + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')"); return new QueryStatement>(sql) { @Override public void prepare(PreparedStatement statement) throws SQLException { @@ -869,7 +870,7 @@ public class SessionQueries { }; } - public static Query> summaryOfPlayers(Set playerUUIDs, long after, long before) { + public static Query> summaryOfPlayers(Set playerUUIDs, List serverUUIDs, long after, long before) { String selectAggregates = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime," + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime," + @@ -878,7 +879,8 @@ public class SessionQueries { WHERE + SessionsTable.SESSION_START + ">?" + AND + SessionsTable.SESSION_END + "" + AND + SessionsTable.USER_UUID + " IN ('" + - new TextStringBuilder().appendWithSeparators(playerUUIDs, "','").build() + "')"; + new TextStringBuilder().appendWithSeparators(playerUUIDs, "','").build() + "')" + + (serverUUIDs.isEmpty() ? "" : AND + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')"); return new QueryStatement>(selectAggregates) { @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserInfoQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserInfoQueries.java index 94f7feb1d..41acabf27 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserInfoQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/UserInfoQueries.java @@ -22,6 +22,7 @@ import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryStatement; import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable; +import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.utilities.java.Lists; import org.apache.commons.text.TextStringBuilder; @@ -338,4 +339,28 @@ public class UserInfoQueries { } }; } + + public static Query> uuidsOfRegisteredBetween(long after, long before, List serverUUIDs) { + String sql = SELECT + DISTINCT + UserInfoTable.USER_UUID + + FROM + UserInfoTable.TABLE_NAME + + WHERE + UserInfoTable.REGISTERED + ">=?" + + AND + UserInfoTable.REGISTERED + "<=?" + + AND + UserInfoTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')"; + return new QueryStatement>(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setLong(1, after); + statement.setLong(2, before); + } + + @Override + public Set processResults(ResultSet set) throws SQLException { + Set uuids = new HashSet<>(); + while (set.next()) { + uuids.add(UUID.fromString(set.getString(UsersTable.USER_UUID))); + } + return uuids; + } + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/QueryTablePlayersQuery.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/QueryTablePlayersQuery.java index b28ec7126..5d2416fcb 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/QueryTablePlayersQuery.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/playertable/QueryTablePlayersQuery.java @@ -18,6 +18,7 @@ package com.djrapitops.plan.storage.database.queries.objects.playertable; import com.djrapitops.plan.delivery.domain.TablePlayer; import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; +import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.SQLDB; import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.QueryStatement; @@ -46,6 +47,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*; public class QueryTablePlayersQuery implements Query> { private final Collection playerUUIDs; + private final List serverUUIDs; private final long afterDate; private final long beforeDate; private final long activeMsThreshold; @@ -54,12 +56,14 @@ public class QueryTablePlayersQuery implements Query> { * Create a new query. * * @param playerUUIDs UUIDs of the players in the query - * @param beforeDate View data before this epoch ms + * @param serverUUIDs View data for these Server UUIDs * @param afterDate View data after this epoch ms + * @param beforeDate View data before this epoch ms * @param activeMsThreshold Playtime threshold for Activity Index calculation */ - public QueryTablePlayersQuery(Collection playerUUIDs, long afterDate, long beforeDate, long activeMsThreshold) { + public QueryTablePlayersQuery(Collection playerUUIDs, List serverUUIDs, long afterDate, long beforeDate, long activeMsThreshold) { this.playerUUIDs = playerUUIDs; + this.serverUUIDs = serverUUIDs; this.afterDate = afterDate; this.beforeDate = beforeDate; this.activeMsThreshold = activeMsThreshold; @@ -95,12 +99,14 @@ public class QueryTablePlayersQuery implements Query> { AND + "s." + SessionsTable.SESSION_END + "<=?" + AND + "s." + SessionsTable.USER_UUID + uuidsInSet + + (serverUUIDs.isEmpty() ? "" : AND + "s." + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')") + GROUP_BY + "s." + SessionsTable.USER_UUID; String selectBanned = SELECT + DISTINCT + "ub." + UserInfoTable.USER_UUID + FROM + UserInfoTable.TABLE_NAME + " ub" + WHERE + UserInfoTable.BANNED + "=?" + - AND + UserInfoTable.USER_UUID + uuidsInSet; + AND + UserInfoTable.USER_UUID + uuidsInSet + + (serverUUIDs.isEmpty() ? "" : AND + UserInfoTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')"); String selectBaseUsers = SELECT + "u." + UsersTable.USER_UUID + ',' + diff --git a/Plan/common/src/main/resources/assets/plan/web/css/style.css b/Plan/common/src/main/resources/assets/plan/web/css/style.css index 78c8d42c4..b893cfa83 100644 --- a/Plan/common/src/main/resources/assets/plan/web/css/style.css +++ b/Plan/common/src/main/resources/assets/plan/web/css/style.css @@ -869,6 +869,30 @@ div#navSrvContainer::-webkit-scrollbar-thumb { color: #fff; } +.btn.bg-plan:hover, +.btn.bg-pink:hover, +.btn.bg-red:hover, +.btn.bg-purple:hover, +.btn.bg-deep-purple:hover, +.btn.bg-indigo:hover, +.btn.bg-light-blue:hover, +.btn.bg-black:hover, +.btn.bg-blue:hover, +.btn.bg-cyan:hover, +.btn.bg-teal:hover, +.btn.bg-green:hover, +.btn.bg-light-green:hover, +.btn.bg-lime:hover, +.btn.bg-yellow:hover, +.btn.bg-amber:hover, +.btn.bg-orange:hover, +.btn.bg-deep-orange:hover, +.btn.bg-brown:hover, +.btn.bg-grey:hover, +.btn.bg-blue-grey:hover { + color: #ccc; +} + .bg-night, body.theme-night .fc-toolbar-chunk .btn.btn-primary { background-color: #44475a; color: #eee8d5; diff --git a/Plan/common/src/main/resources/assets/plan/web/js/filters.js b/Plan/common/src/main/resources/assets/plan/web/js/filters.js index 4b1a407f1..cc6fb40cd 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/filters.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/filters.js @@ -209,6 +209,12 @@ class RegisteredBetweenFilter extends BetweenDateFilter { } } +class PlayedOnServerFilter extends MultipleChoiceFilter { + constructor(id, options) { + super(id, "playedOnServer", "have played on at least one of", options); + } +} + function createFilter(filter, id) { if (filter.kind.startsWith("pluginGroups-")) { return new PluginGroupsFilter(id, filter.kind, filter.options); @@ -230,6 +236,8 @@ function createFilter(filter, id) { return new RegisteredBetweenFilter(id, filter.options); case "pluginsBooleanGroups": return new PluginBooleanGroupsFilter(id, filter.options); + case "playedOnServer": + return new PlayedOnServerFilter(id, filter.options); default: throw new Error("Unsupported filter kind: '" + filter.kind + "'"); } @@ -258,6 +266,8 @@ function getReadableFilterName(filter) { return "Registered between"; case "pluginsBooleanGroups": return "Has plugin boolean value"; + case "playedOnServer": + return "Has played on one of servers"; default: return filter.kind; } diff --git a/Plan/common/src/main/resources/assets/plan/web/js/query.js b/Plan/common/src/main/resources/assets/plan/web/js/query.js index 334eb32ae..a185d42ae 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/query.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/query.js @@ -6,7 +6,8 @@ const queryState = { afterDate: null, afterTime: null, beforeDate: null, - beforeTime: null + beforeTime: null, + servers: [] }, invalidFormFields: { ids: [], @@ -34,6 +35,8 @@ const queryState = { let timestamp = undefined; +let serverMap = {}; + function loadView(json) { queryState.view = json.view; @@ -42,11 +45,36 @@ function loadView(json) { document.getElementById('viewToDateField').setAttribute('placeholder', json.view.beforeDate); document.getElementById('viewToTimeField').setAttribute('placeholder', json.view.beforeTime); + // Load server selector or hide it + if (json.view.servers.length >= 2) { + let options = ``; + for (let server of json.view.servers) { + if (server.proxy) continue; + serverMap[server.serverUUID] = server; + options += `${server.serverName}` + } + const serverSelector = document.getElementById("server-selector"); + serverSelector.innerHTML = options; + + serverSelector.addEventListener('click', () => { + queryState.view.servers = []; + if (serverSelector.selectedOptions.length !== serverSelector.options.length) { + for (const option of serverSelector.selectedOptions) { + queryState.view.servers.push(serverMap[option.getAttribute('data-plan-server-uuid')]); + } + } + document.getElementById("serverDropdown").innerText = queryState.view.servers.length + ? `using data of ${queryState.view.servers.length} server(s)` + : "using data of all servers" + }) + } else { + document.getElementById("serverDropdown").classList.add("hidden"); + } + const playersOnlineSeries = { name: 'Players Online', type: 'areaspline', tooltip: {valueDecimals: 0}, data: json.viewPoints, color: '#9E9E9E', yAxis: 0 } - graphs.push(Highcharts.stockChart('viewChart', { rangeSelector: { selected: 3, @@ -424,7 +452,8 @@ function displayDataResultScreen(resultCount, view) { - View: ${afterDate} - ${beforeDate} + View: ${afterDate} - ${beforeDate}, +${view.servers.length ? "using data of servers: " + view.servers.map(server => server.serverName).join(', ') : "using data of all servers"} diff --git a/Plan/common/src/main/resources/assets/plan/web/query.html b/Plan/common/src/main/resources/assets/plan/web/query.html index 79cbd27b3..742389490 100644 --- a/Plan/common/src/main/resources/assets/plan/web/query.html +++ b/Plan/common/src/main/resources/assets/plan/web/query.html @@ -141,6 +141,21 @@ + + using data of all servers + + + + Proxy server + Server 1 + Skyblock + Server 3 + + + diff --git a/Plan/common/src/test/java/utilities/dagger/TestSystemObjectProvidingModule.java b/Plan/common/src/test/java/utilities/dagger/TestSystemObjectProvidingModule.java index 4ba2e8e82..13f106aec 100644 --- a/Plan/common/src/test/java/utilities/dagger/TestSystemObjectProvidingModule.java +++ b/Plan/common/src/test/java/utilities/dagger/TestSystemObjectProvidingModule.java @@ -22,6 +22,7 @@ import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.LocaleSystem; import com.djrapitops.plan.storage.file.JarResource; import com.djrapitops.plan.utilities.logging.ErrorLogger; +import com.google.gson.Gson; import dagger.Module; import dagger.Provides; import utilities.TestErrorLogger; @@ -37,6 +38,12 @@ import java.util.function.Predicate; @Module public class TestSystemObjectProvidingModule { + @Provides + @Singleton + Gson provideGson() { + return new Gson(); + } + @Provides @Singleton Locale provideLocale(LocaleSystem localeSystem) {