mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-03-11 14:20:54 +01:00
/v1/query endpoint
- Requires 'q' parameter which is URI encoded JSON array - The array contains FilterQuery objects - Right now the list of UUIDs and path is returned Up next /v1/filters endpoint that returns list of filter kinds and what their default options.
This commit is contained in:
parent
aac7bdc632
commit
a14d7d4769
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
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.storage.database.queries.filter.Filter;
|
||||
import com.djrapitops.plan.storage.database.queries.filter.FilterQuery;
|
||||
import com.djrapitops.plan.storage.database.queries.filter.QueryFilters;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
public class QueryJSONResolver implements Resolver {
|
||||
|
||||
private QueryFilters filters;
|
||||
|
||||
@Inject
|
||||
public QueryJSONResolver(
|
||||
QueryFilters filters
|
||||
) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
WebUser user = request.getUser().orElse(new WebUser(""));
|
||||
return user.hasPermission("page.players");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
return Optional.of(getResponse(request));
|
||||
}
|
||||
|
||||
private Response getResponse(Request request) {
|
||||
String q = request.getQuery().get("q").orElseThrow(() -> new BadRequestException("'q' parameter not set (expecting json array)"));
|
||||
try {
|
||||
q = URLDecoder.decode(q, "UTF-8");
|
||||
List<FilterQuery> queries = FilterQuery.parse(q);
|
||||
Filter.Result result = filters.apply(queries);
|
||||
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(Maps.builder(String.class, Object.class)
|
||||
.put("path", result.getResultPath(","))
|
||||
.put("uuids", result.getResultUUIDs())
|
||||
.build())
|
||||
.build();
|
||||
} catch (IOException e) {
|
||||
throw new BadRequestException("Failed to parse json: '" + q + "'" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,8 @@ public class RootJSONResolver {
|
||||
PerformanceJSONCreator performanceJSONCreator,
|
||||
|
||||
PlayerJSONResolver playerJSONResolver,
|
||||
NetworkJSONResolver networkJSONResolver
|
||||
NetworkJSONResolver networkJSONResolver,
|
||||
QueryJSONResolver queryJSONResolver
|
||||
) {
|
||||
this.identifiers = identifiers;
|
||||
|
||||
@ -70,6 +71,7 @@ public class RootJSONResolver {
|
||||
.add("performanceOverview", forJSON(DataID.PERFORMANCE_OVERVIEW, performanceJSONCreator))
|
||||
.add("player", playerJSONResolver)
|
||||
.add("network", networkJSONResolver.getResolver())
|
||||
.add("query", queryJSONResolver)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
package com.djrapitops.plan.storage.database.queries.filter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -32,8 +32,8 @@ public interface Filter {
|
||||
|
||||
String[] getExpectedParameters();
|
||||
|
||||
default List<String> getOptions() {
|
||||
return Collections.emptyList();
|
||||
default Map<String, Object> getOptions() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,4 +46,51 @@ public interface Filter {
|
||||
*/
|
||||
Set<UUID> getMatchingUUIDs(FilterQuery query);
|
||||
|
||||
default Result apply(FilterQuery query) {
|
||||
return new Result(null, getKind(), getMatchingUUIDs(query));
|
||||
}
|
||||
|
||||
class Result {
|
||||
private Result previous;
|
||||
|
||||
private String filterKind;
|
||||
private int resultSize;
|
||||
private Set<UUID> currentUUIDs;
|
||||
|
||||
private Result(Result previous, String filterKind, Set<UUID> currentUUIDs) {
|
||||
this.previous = previous;
|
||||
this.filterKind = filterKind;
|
||||
this.resultSize = currentUUIDs.size();
|
||||
this.currentUUIDs = currentUUIDs;
|
||||
}
|
||||
|
||||
public Result apply(Filter filter, FilterQuery query) {
|
||||
Set<UUID> got = filter.getMatchingUUIDs(query);
|
||||
currentUUIDs.retainAll(got);
|
||||
return new Result(this, filter.getKind(), currentUUIDs);
|
||||
}
|
||||
|
||||
public Result notApplied(Filter filter) {
|
||||
return new Result(this, filter.getKind(), currentUUIDs);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return resultSize <= 0;
|
||||
}
|
||||
|
||||
public Set<UUID> getResultUUIDs() {
|
||||
return currentUUIDs;
|
||||
}
|
||||
|
||||
public StringBuilder getResultPath(String separator) {
|
||||
StringBuilder builder;
|
||||
if (previous == null) {
|
||||
// First Result in chain
|
||||
builder = new StringBuilder();
|
||||
} else {
|
||||
builder = previous.getResultPath(separator);
|
||||
}
|
||||
return builder.append(separator).append("-> ").append(filterKind).append(": ").append(resultSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents parameters for a single {@link Filter} parsed from the query json.
|
||||
@ -48,6 +49,12 @@ public class FilterQuery {
|
||||
}
|
||||
|
||||
public Optional<String> get(String key) {
|
||||
if (parameters == null) return Optional.empty();
|
||||
return Optional.ofNullable(parameters.get(key));
|
||||
}
|
||||
|
||||
public Set<String> getSetParameters() {
|
||||
if (parameters == null) return null;
|
||||
return parameters.keySet();
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,11 @@
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database.queries.filter;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Contains a single instance of each filter kind.
|
||||
@ -50,4 +49,39 @@ public class QueryFilters {
|
||||
return Optional.ofNullable(filters.get(kind));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply queries to get a {@link com.djrapitops.plan.storage.database.queries.filter.Filter.Result}.
|
||||
*
|
||||
* @param filterQueries FilterQueries to use as filter parameters.
|
||||
* @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<FilterQuery> filterQueries) {
|
||||
Filter.Result current = null;
|
||||
for (FilterQuery filterQuery : filterQueries) {
|
||||
current = apply(current, filterQuery);
|
||||
if (current != null && current.isEmpty()) break;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private Filter.Result apply(Filter.Result current, FilterQuery filterQuery) {
|
||||
String kind = filterQuery.getKind();
|
||||
Filter filter = getFilter(kind).orElseThrow(() -> new BadRequestException("Filter kind not supported: '" + kind + "'"));
|
||||
|
||||
current = getResult(current, filter, filterQuery);
|
||||
return current;
|
||||
}
|
||||
|
||||
private Filter.Result getResult(Filter.Result current, Filter filter, FilterQuery query) {
|
||||
try {
|
||||
return current == null ? filter.apply(query) : current.apply(filter, query);
|
||||
} catch (IllegalArgumentException badOptions) {
|
||||
throw new BadRequestException("Bad parameters for filter '" + filter.getKind() +
|
||||
"': expecting " + Arrays.asList(filter.getExpectedParameters()) +
|
||||
", but was given " + query.getSetParameters());
|
||||
} catch (CompleteSetException complete) {
|
||||
return current == null ? null : current.notApplied(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,8 +58,8 @@ public class ActivityIndexFilter extends MultiOptionFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOptions() {
|
||||
return Collections.singletonList(serializeOptions(getOptionsArray()));
|
||||
public Map<String, Object> getOptions() {
|
||||
return Collections.singletonMap("options", getOptionsArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,8 +52,8 @@ public class BannedFilter extends MultiOptionFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOptions() {
|
||||
return Collections.singletonList(serializeOptions(getOptionsArray()));
|
||||
public Map<String, Object> getOptions() {
|
||||
return Collections.singletonMap("options", getOptionsArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,12 +20,12 @@ import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.filter.Filter;
|
||||
import com.djrapitops.plan.storage.database.queries.filter.FilterQuery;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class DateRangeFilter implements Filter {
|
||||
|
||||
@ -48,13 +48,16 @@ public abstract class DateRangeFilter implements Filter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOptions() {
|
||||
public Map<String, Object> getOptions() {
|
||||
long earliestData = dbSystem.getDatabase().query(BaseUserQueries.minimumRegisterDate());
|
||||
long now = System.currentTimeMillis();
|
||||
if (earliestData == -1) earliestData = now;
|
||||
String[] afterDate = StringUtils.split(dateFormat.format(earliestData), ' ');
|
||||
String[] beforeDate = StringUtils.split(dateFormat.format(now), ' ');
|
||||
return Arrays.asList(afterDate[0], afterDate[1], beforeDate[0], beforeDate[1]);
|
||||
return Maps.builder(String.class, Object.class)
|
||||
.put("after", afterDate)
|
||||
.put("before", beforeDate)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected long getAfter(FilterQuery query) {
|
||||
|
@ -19,7 +19,6 @@ package com.djrapitops.plan.storage.database.queries.filter.filters;
|
||||
import com.djrapitops.plan.storage.database.queries.filter.Filter;
|
||||
import com.djrapitops.plan.storage.database.queries.filter.FilterQuery;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.TextStringBuilder;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -36,10 +35,6 @@ public abstract class MultiOptionFilter implements Filter {
|
||||
return Arrays.asList(deserializeOptions(selected));
|
||||
}
|
||||
|
||||
protected String serializeOptions(String... options) {
|
||||
return new TextStringBuilder().appendWithSeparators(options, ",").build();
|
||||
}
|
||||
|
||||
private String[] deserializeOptions(String selected) {
|
||||
return StringUtils.split(selected, ',');
|
||||
}
|
||||
|
@ -52,8 +52,8 @@ public class OperatorsFilter extends MultiOptionFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOptions() {
|
||||
return Collections.singletonList(serializeOptions(getOptionsArray()));
|
||||
public Map<String, Object> getOptions() {
|
||||
return Collections.singletonMap("options", getOptionsArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.filter.FilterQuery;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -45,7 +46,7 @@ public class PluginGroupsFilter extends MultiOptionFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getOptions() {
|
||||
public Map<String, Object> getOptions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user