mirror of https://github.com/halo-dev/halo
Cleanup code with SortableRequest (#6540)
#### What type of PR is this? /kind cleanup /kind improvement /area core /milestone 2.19.0 #### What this PR does / why we need it: This PR refactors some requests with sort parameter by reusing SortableRequest, and refactors some queries with indexer. #### Does this PR introduce a user-facing change? ```release-note None ```pull/6542/head
parent
15a3e78e61
commit
f61f846a7f
|
@ -1,5 +1,8 @@
|
||||||
package run.halo.app.extension;
|
package run.halo.app.extension;
|
||||||
|
|
||||||
|
import static org.springframework.data.domain.Sort.Order.asc;
|
||||||
|
import static org.springframework.data.domain.Sort.Order.desc;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -53,8 +56,8 @@ public enum ExtensionUtil {
|
||||||
*/
|
*/
|
||||||
public static Sort defaultSort() {
|
public static Sort defaultSort() {
|
||||||
return Sort.by(
|
return Sort.by(
|
||||||
Sort.Order.desc("metadata.creationTimestamp"),
|
desc("metadata.creationTimestamp"),
|
||||||
Sort.Order.asc("metadata.name")
|
asc("metadata.name")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package run.halo.app.extension.router;
|
package run.halo.app.extension.router;
|
||||||
|
|
||||||
import static org.springframework.data.domain.Sort.Order.asc;
|
|
||||||
import static org.springframework.data.domain.Sort.Order.desc;
|
|
||||||
import static run.halo.app.extension.Comparators.compareCreationTimestamp;
|
import static run.halo.app.extension.Comparators.compareCreationTimestamp;
|
||||||
import static run.halo.app.extension.Comparators.compareName;
|
import static run.halo.app.extension.Comparators.compareName;
|
||||||
import static run.halo.app.extension.Comparators.nullsComparator;
|
import static run.halo.app.extension.Comparators.nullsComparator;
|
||||||
|
import static run.halo.app.extension.ExtensionUtil.defaultSort;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||||
|
|
||||||
|
@ -43,9 +42,7 @@ public class SortableRequest extends IListRequest.QueryListRequest {
|
||||||
example = "metadata.creationTimestamp,desc"))
|
example = "metadata.creationTimestamp,desc"))
|
||||||
public Sort getSort() {
|
public Sort getSort() {
|
||||||
return SortResolver.defaultInstance.resolve(exchange)
|
return SortResolver.defaultInstance.resolve(exchange)
|
||||||
.and(Sort.by(desc("metadata.creationTimestamp"),
|
.and(defaultSort());
|
||||||
asc("metadata.name"))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,27 +1,23 @@
|
||||||
package run.halo.app.content;
|
package run.halo.app.content;
|
||||||
|
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
|
import static run.halo.app.core.extension.content.Post.PUBLISHED_LABEL;
|
||||||
|
import static run.halo.app.core.extension.content.Post.PostPhase.PENDING_APPROVAL;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.contains;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.or;
|
||||||
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import java.util.Optional;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springdoc.core.fn.builders.operation.Builder;
|
import org.springdoc.core.fn.builders.operation.Builder;
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.endpoint.SortResolver;
|
|
||||||
import run.halo.app.extension.ListOptions;
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.index.query.QueryFactory;
|
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.IListRequest;
|
||||||
import run.halo.app.extension.router.selector.FieldSelector;
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
import run.halo.app.extension.router.selector.LabelSelector;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A query object for {@link Post} list.
|
* A query object for {@link Post} list.
|
||||||
|
@ -29,9 +25,7 @@ import run.halo.app.extension.router.selector.LabelSelector;
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class PostQuery extends IListRequest.QueryListRequest {
|
public class PostQuery extends SortableRequest {
|
||||||
|
|
||||||
private final ServerWebExchange exchange;
|
|
||||||
|
|
||||||
private final String username;
|
private final String username;
|
||||||
|
|
||||||
|
@ -40,21 +34,13 @@ public class PostQuery extends IListRequest.QueryListRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PostQuery(ServerRequest request, @Nullable String username) {
|
public PostQuery(ServerRequest request, @Nullable String username) {
|
||||||
super(request.queryParams());
|
super(request.exchange());
|
||||||
this.exchange = request.exchange();
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(hidden = true)
|
|
||||||
@JsonIgnore
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Post.PostPhase getPublishPhase() {
|
public String getPublishPhase() {
|
||||||
String publishPhase = queryParams.getFirst("publishPhase");
|
return queryParams.getFirst("publishPhase");
|
||||||
return Post.PostPhase.from(publishPhase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -64,72 +50,48 @@ public class PostQuery extends IListRequest.QueryListRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Schema(description = "Posts filtered by keyword.")
|
|
||||||
public String getKeyword() {
|
public String getKeyword() {
|
||||||
return StringUtils.defaultIfBlank(queryParams.getFirst("keyword"), null);
|
return StringUtils.defaultIfBlank(queryParams.getFirst("keyword"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ArraySchema(uniqueItems = true,
|
|
||||||
arraySchema = @Schema(name = "sort",
|
|
||||||
description = "Sort property and direction of the list result. Supported fields: "
|
|
||||||
+ "creationTimestamp,publishTime"),
|
|
||||||
schema = @Schema(description = "like field,asc or field,desc",
|
|
||||||
implementation = String.class,
|
|
||||||
example = "creationTimestamp,desc"))
|
|
||||||
public Sort getSort() {
|
|
||||||
var sort = SortResolver.defaultInstance.resolve(exchange);
|
|
||||||
sort = sort.and(Sort.by(Sort.Direction.DESC, "metadata.creationTimestamp"));
|
|
||||||
sort = sort.and(Sort.by(Sort.Direction.DESC, "metadata.name"));
|
|
||||||
return sort;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a list options from the query object.
|
* Build a list options from the query object.
|
||||||
*
|
*
|
||||||
* @return a list options
|
* @return a list options
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public ListOptions toListOptions() {
|
public ListOptions toListOptions() {
|
||||||
var listOptions =
|
var builder = ListOptions.builder(super.toListOptions());
|
||||||
labelAndFieldSelectorToListOptions(getLabelSelector(), getFieldSelector());
|
|
||||||
if (listOptions.getFieldSelector() == null) {
|
|
||||||
listOptions.setFieldSelector(FieldSelector.all());
|
|
||||||
}
|
|
||||||
var labelSelectorBuilder = LabelSelector.builder();
|
|
||||||
var fieldQuery = QueryFactory.all();
|
|
||||||
|
|
||||||
String keyword = getKeyword();
|
Optional.ofNullable(getKeyword())
|
||||||
if (keyword != null) {
|
.filter(StringUtils::isNotBlank)
|
||||||
fieldQuery = QueryFactory.and(fieldQuery, QueryFactory.or(
|
.ifPresent(keyword -> builder.andQuery(or(
|
||||||
QueryFactory.contains("status.excerpt", keyword),
|
contains("status.excerpt", keyword),
|
||||||
QueryFactory.contains("spec.slug", keyword),
|
contains("spec.slug", keyword),
|
||||||
QueryFactory.contains("spec.title", keyword)
|
contains("spec.title", keyword)
|
||||||
));
|
)));
|
||||||
}
|
|
||||||
|
|
||||||
Post.PostPhase publishPhase = getPublishPhase();
|
Optional.ofNullable(getPublishPhase())
|
||||||
if (publishPhase != null) {
|
.filter(StringUtils::isNotBlank)
|
||||||
if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) {
|
.map(Post.PostPhase::from)
|
||||||
fieldQuery = QueryFactory.and(fieldQuery, QueryFactory.equal(
|
.ifPresent(phase -> {
|
||||||
"status.phase", Post.PostPhase.PENDING_APPROVAL.name())
|
if (PENDING_APPROVAL.equals(phase)) {
|
||||||
|
builder.andQuery(equal("status.phase", phase.name()));
|
||||||
|
}
|
||||||
|
var labelSelector = builder.labelSelector();
|
||||||
|
Optional.of(phase)
|
||||||
|
.filter(Post.PostPhase.PUBLISHED::equals)
|
||||||
|
.ifPresentOrElse(
|
||||||
|
published -> labelSelector.eq(PUBLISHED_LABEL, Boolean.TRUE.toString()),
|
||||||
|
() -> labelSelector.notEq(PUBLISHED_LABEL, Boolean.TRUE.toString())
|
||||||
);
|
);
|
||||||
labelSelectorBuilder.eq(Post.PUBLISHED_LABEL, BooleanUtils.FALSE);
|
});
|
||||||
} else if (Post.PostPhase.PUBLISHED.equals(publishPhase)) {
|
|
||||||
labelSelectorBuilder.eq(Post.PUBLISHED_LABEL, BooleanUtils.TRUE);
|
|
||||||
} else {
|
|
||||||
labelSelectorBuilder.eq(Post.PUBLISHED_LABEL, BooleanUtils.FALSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(username)) {
|
Optional.ofNullable(username)
|
||||||
fieldQuery = QueryFactory.and(fieldQuery, QueryFactory.equal(
|
.filter(StringUtils::isNotBlank)
|
||||||
"spec.owner", username)
|
.ifPresent(username -> builder.andQuery(equal("spec.owner", username)));
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
listOptions.setFieldSelector(listOptions.getFieldSelector().andQuery(fieldQuery));
|
return builder.build();
|
||||||
listOptions.setLabelSelector(
|
|
||||||
listOptions.getLabelSelector().and(labelSelectorBuilder.build()));
|
|
||||||
return listOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void buildParameters(Builder builder) {
|
public static void buildParameters(Builder builder) {
|
||||||
|
|
|
@ -1,30 +1,25 @@
|
||||||
package run.halo.app.content;
|
package run.halo.app.content;
|
||||||
|
|
||||||
import static java.util.Comparator.comparing;
|
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
|
import static run.halo.app.core.extension.content.Post.PostPhase.PENDING_APPROVAL;
|
||||||
|
import static run.halo.app.core.extension.content.SinglePage.PUBLISHED_LABEL;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.in;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.or;
|
||||||
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import java.util.Optional;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springdoc.core.fn.builders.operation.Builder;
|
import org.springdoc.core.fn.builders.operation.Builder;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
import run.halo.app.core.extension.endpoint.SortResolver;
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.Comparators;
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.IListRequest;
|
||||||
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query parameter for {@link SinglePage} list.
|
* Query parameter for {@link SinglePage} list.
|
||||||
|
@ -32,148 +27,67 @@ import run.halo.app.extension.router.IListRequest;
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class SinglePageQuery extends IListRequest.QueryListRequest {
|
public class SinglePageQuery extends SortableRequest {
|
||||||
|
|
||||||
private final ServerWebExchange exchange;
|
|
||||||
|
|
||||||
public SinglePageQuery(ServerRequest request) {
|
public SinglePageQuery(ServerRequest request) {
|
||||||
super(request.queryParams());
|
super(request.exchange());
|
||||||
this.exchange = request.exchange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Override
|
||||||
@Schema(name = "contributor")
|
public ListOptions toListOptions() {
|
||||||
public Set<String> getContributors() {
|
var builder = ListOptions.builder(super.toListOptions());
|
||||||
List<String> contributorList = queryParams.get("contributor");
|
|
||||||
return contributorList == null ? null : Set.copyOf(contributorList);
|
Optional.ofNullable(queryParams.getFirst("keyword"))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.ifPresent(keyword -> builder.andQuery(or(
|
||||||
|
QueryFactory.contains("spec.title", keyword),
|
||||||
|
QueryFactory.contains("spec.slug", keyword),
|
||||||
|
QueryFactory.contains("status.excerpt", keyword)
|
||||||
|
)));
|
||||||
|
|
||||||
|
Optional.ofNullable(queryParams.getFirst("publishPhase"))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.map(Post.PostPhase::from)
|
||||||
|
.ifPresent(phase -> {
|
||||||
|
if (PENDING_APPROVAL.equals(phase)) {
|
||||||
|
builder.andQuery(equal("status.phase", phase.name()));
|
||||||
|
}
|
||||||
|
var labelSelector = builder.labelSelector();
|
||||||
|
Optional.of(phase)
|
||||||
|
.filter(Post.PostPhase.PUBLISHED::equals)
|
||||||
|
.ifPresentOrElse(
|
||||||
|
published -> labelSelector.eq(PUBLISHED_LABEL, Boolean.TRUE.toString()),
|
||||||
|
() -> labelSelector.notEq(PUBLISHED_LABEL, Boolean.TRUE.toString())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Optional.ofNullable(queryParams.getFirst("visible"))
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.map(Post.VisibleEnum::from)
|
||||||
|
.ifPresent(visible -> builder.andQuery(equal("spec.visible", visible.name())));
|
||||||
|
|
||||||
|
Optional.ofNullable(queryParams.get("contributor"))
|
||||||
|
.filter(contributors -> !contributors.isEmpty())
|
||||||
|
.ifPresent(contributors -> builder.andQuery(in("status.contributors", contributors)));
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Override
|
||||||
public Post.PostPhase getPublishPhase() {
|
|
||||||
String publishPhase = queryParams.getFirst("publishPhase");
|
|
||||||
return Post.PostPhase.from(publishPhase);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Post.VisibleEnum getVisible() {
|
|
||||||
String visible = queryParams.getFirst("visible");
|
|
||||||
return Post.VisibleEnum.from(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Schema(description = "SinglePages filtered by keyword.")
|
|
||||||
public String getKeyword() {
|
|
||||||
return StringUtils.defaultIfBlank(queryParams.getFirst("keyword"), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ArraySchema(uniqueItems = true,
|
|
||||||
arraySchema = @Schema(name = "sort",
|
|
||||||
description = "Sort property and direction of the list result. Supported fields: "
|
|
||||||
+ "creationTimestamp,publishTime"),
|
|
||||||
schema = @Schema(description = "like field,asc or field,desc",
|
|
||||||
implementation = String.class,
|
|
||||||
example = "creationTimestamp,desc"))
|
|
||||||
public Sort getSort() {
|
public Sort getSort() {
|
||||||
return SortResolver.defaultInstance.resolve(exchange);
|
var sort = super.getSort();
|
||||||
|
var orders = sort.stream()
|
||||||
|
.map(order -> {
|
||||||
|
if ("creationTimestamp".equals(order.getProperty())) {
|
||||||
|
return order.withProperty("metadata.creationTimestamp");
|
||||||
}
|
}
|
||||||
|
if ("publishTime".equals(order.getProperty())) {
|
||||||
/**
|
return order.withProperty("spec.publishTime");
|
||||||
* Build a comparator for {@link SinglePageQuery}.
|
|
||||||
*
|
|
||||||
* @return comparator
|
|
||||||
*/
|
|
||||||
public Comparator<SinglePage> toComparator() {
|
|
||||||
var sort = getSort();
|
|
||||||
var creationTimestampOrder = sort.getOrderFor("creationTimestamp");
|
|
||||||
List<Comparator<SinglePage>> comparators = new ArrayList<>();
|
|
||||||
if (creationTimestampOrder != null) {
|
|
||||||
Comparator<SinglePage> comparator =
|
|
||||||
comparing(page -> page.getMetadata().getCreationTimestamp());
|
|
||||||
if (creationTimestampOrder.isDescending()) {
|
|
||||||
comparator = comparator.reversed();
|
|
||||||
}
|
}
|
||||||
comparators.add(comparator);
|
return order;
|
||||||
}
|
})
|
||||||
|
.toList();
|
||||||
var publishTimeOrder = sort.getOrderFor("publishTime");
|
return Sort.by(orders);
|
||||||
if (publishTimeOrder != null) {
|
|
||||||
Comparator<Object> nullsComparator = publishTimeOrder.isAscending()
|
|
||||||
? org.springframework.util.comparator.Comparators.nullsLow()
|
|
||||||
: org.springframework.util.comparator.Comparators.nullsHigh();
|
|
||||||
Comparator<SinglePage> comparator =
|
|
||||||
comparing(page -> page.getSpec().getPublishTime(), nullsComparator);
|
|
||||||
if (publishTimeOrder.isDescending()) {
|
|
||||||
comparator = comparator.reversed();
|
|
||||||
}
|
|
||||||
comparators.add(comparator);
|
|
||||||
}
|
|
||||||
comparators.add(Comparators.compareCreationTimestamp(false));
|
|
||||||
comparators.add(Comparators.compareName(true));
|
|
||||||
return comparators.stream()
|
|
||||||
.reduce(Comparator::thenComparing)
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a predicate for {@link SinglePageQuery}.
|
|
||||||
*
|
|
||||||
* @return predicate
|
|
||||||
*/
|
|
||||||
public Predicate<SinglePage> toPredicate() {
|
|
||||||
Predicate<SinglePage> paramPredicate = singlePage -> contains(getContributors(),
|
|
||||||
singlePage.getStatusOrDefault().getContributors());
|
|
||||||
|
|
||||||
String keyword = getKeyword();
|
|
||||||
if (keyword != null) {
|
|
||||||
paramPredicate = paramPredicate.and(page -> {
|
|
||||||
String excerpt = page.getStatusOrDefault().getExcerpt();
|
|
||||||
return StringUtils.containsIgnoreCase(excerpt, keyword)
|
|
||||||
|| StringUtils.containsIgnoreCase(page.getSpec().getSlug(), keyword)
|
|
||||||
|| StringUtils.containsIgnoreCase(page.getSpec().getTitle(), keyword);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Post.PostPhase publishPhase = getPublishPhase();
|
|
||||||
if (publishPhase != null) {
|
|
||||||
paramPredicate = paramPredicate.and(page -> {
|
|
||||||
if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) {
|
|
||||||
return !page.isPublished()
|
|
||||||
&& Post.PostPhase.PENDING_APPROVAL.name()
|
|
||||||
.equalsIgnoreCase(page.getStatusOrDefault().getPhase());
|
|
||||||
}
|
|
||||||
// published
|
|
||||||
if (Post.PostPhase.PUBLISHED.equals(publishPhase)) {
|
|
||||||
return page.isPublished();
|
|
||||||
}
|
|
||||||
// draft
|
|
||||||
return !page.isPublished();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Post.VisibleEnum visible = getVisible();
|
|
||||||
if (visible != null) {
|
|
||||||
paramPredicate =
|
|
||||||
paramPredicate.and(post -> visible.equals(post.getSpec().getVisible()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate<SinglePage> predicate = labelAndFieldSelectorToPredicate(getLabelSelector(),
|
|
||||||
getFieldSelector());
|
|
||||||
return predicate.and(paramPredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean contains(Collection<String> left, List<String> right) {
|
|
||||||
// parameter is null, it means that ignore this condition
|
|
||||||
if (left == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// else, it means that right is empty
|
|
||||||
if (left.isEmpty()) {
|
|
||||||
return right.isEmpty();
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return right.stream().anyMatch(left::contains);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void buildParameters(Builder builder) {
|
public static void buildParameters(Builder builder) {
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
package run.halo.app.content.comment;
|
package run.halo.app.content.comment;
|
||||||
|
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.and;
|
import static org.springframework.data.domain.Sort.Order.desc;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.contains;
|
import static run.halo.app.extension.index.query.QueryFactory.contains;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
|
import java.util.Optional;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springdoc.core.fn.builders.operation.Builder;
|
import org.springdoc.core.fn.builders.operation.Builder;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import run.halo.app.core.extension.User;
|
import run.halo.app.core.extension.User;
|
||||||
import run.halo.app.core.extension.content.Comment;
|
import run.halo.app.core.extension.content.Comment;
|
||||||
import run.halo.app.core.extension.endpoint.SortResolver;
|
|
||||||
import run.halo.app.extension.ListOptions;
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.PageRequest;
|
|
||||||
import run.halo.app.extension.PageRequestImpl;
|
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.IListRequest;
|
||||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||||
import run.halo.app.extension.router.selector.FieldSelector;
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query criteria for comment list.
|
* Query criteria for comment list.
|
||||||
|
@ -28,64 +25,56 @@ import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class CommentQuery extends IListRequest.QueryListRequest {
|
public class CommentQuery extends SortableRequest {
|
||||||
|
|
||||||
private final ServerWebExchange exchange;
|
|
||||||
|
|
||||||
public CommentQuery(ServerRequest request) {
|
public CommentQuery(ServerRequest request) {
|
||||||
super(request.queryParams());
|
super(request.exchange());
|
||||||
this.exchange = request.exchange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String getKeyword() {
|
public String getKeyword() {
|
||||||
String keyword = queryParams.getFirst("keyword");
|
return queryParams.getFirst("keyword");
|
||||||
return StringUtils.isBlank(keyword) ? null : keyword;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String getOwnerKind() {
|
public String getOwnerKind() {
|
||||||
String ownerKind = queryParams.getFirst("ownerKind");
|
return queryParams.getFirst("ownerKind");
|
||||||
return StringUtils.isBlank(ownerKind) ? null : ownerKind;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String getOwnerName() {
|
public String getOwnerName() {
|
||||||
String ownerName = queryParams.getFirst("ownerName");
|
return queryParams.getFirst("ownerName");
|
||||||
return StringUtils.isBlank(ownerName) ? null : ownerName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Sort getSort() {
|
public Sort getSort() {
|
||||||
var sort = SortResolver.defaultInstance.resolve(exchange);
|
// set default sort by last reply time
|
||||||
return sort.and(Sort.by("status.lastReplyTime",
|
return super.getSort().and(Sort.by(desc("status.lastReplyTime")));
|
||||||
"spec.creationTime",
|
|
||||||
"metadata.name"
|
|
||||||
).descending());
|
|
||||||
}
|
|
||||||
|
|
||||||
public PageRequest toPageRequest() {
|
|
||||||
return PageRequestImpl.of(getPage(), getSize(), getSort());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert to list options.
|
* Convert to list options.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public ListOptions toListOptions() {
|
public ListOptions toListOptions() {
|
||||||
var listOptions =
|
var builder = ListOptions.builder(super.toListOptions());
|
||||||
labelAndFieldSelectorToListOptions(getLabelSelector(), getFieldSelector());
|
|
||||||
var fieldQuery = listOptions.getFieldSelector().query();
|
|
||||||
|
|
||||||
String keyword = getKeyword();
|
Optional.ofNullable(getKeyword())
|
||||||
if (StringUtils.isNotBlank(keyword)) {
|
.filter(StringUtils::isNotBlank)
|
||||||
fieldQuery = and(fieldQuery, contains("spec.raw", keyword));
|
.ifPresent(keyword -> builder.andQuery(contains("spec.raw", keyword)));
|
||||||
}
|
|
||||||
|
|
||||||
String ownerName = getOwnerName();
|
Optional.ofNullable(getOwnerName())
|
||||||
if (StringUtils.isNotBlank(ownerName)) {
|
.filter(StringUtils::isNotBlank)
|
||||||
String ownerKind = StringUtils.defaultIfBlank(getOwnerKind(), User.KIND);
|
.ifPresent(ownerName -> {
|
||||||
fieldQuery = and(fieldQuery,
|
var ownerKind = Optional.ofNullable(getOwnerKind())
|
||||||
equal("spec.owner", Comment.CommentOwner.ownerIdentity(ownerKind, ownerName)));
|
.filter(StringUtils::isNotBlank)
|
||||||
}
|
.orElse(User.KIND);
|
||||||
|
builder.andQuery(
|
||||||
|
equal("spec.owner", Comment.CommentOwner.ownerIdentity(ownerKind, ownerName))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
listOptions.setFieldSelector(FieldSelector.of(fieldQuery));
|
return builder.build();
|
||||||
return listOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void buildParameters(Builder builder) {
|
public static void buildParameters(Builder builder) {
|
||||||
|
|
|
@ -38,7 +38,6 @@ import run.halo.app.core.extension.service.UserService;
|
||||||
import run.halo.app.extension.ListOptions;
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.MetadataOperator;
|
import run.halo.app.extension.MetadataOperator;
|
||||||
import run.halo.app.extension.PageRequestImpl;
|
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.Ref;
|
import run.halo.app.extension.Ref;
|
||||||
import run.halo.app.extension.router.selector.FieldSelector;
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
|
@ -73,9 +72,9 @@ public class PostServiceImpl extends AbstractContentService implements PostServi
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPost>> listPost(PostQuery query) {
|
public Mono<ListResult<ListedPost>> listPost(PostQuery query) {
|
||||||
return buildListOptions(query)
|
return buildListOptions(query)
|
||||||
.flatMap(listOptions -> client.listBy(Post.class, listOptions,
|
.flatMap(listOptions ->
|
||||||
PageRequestImpl.of(query.getPage(), query.getSize(), query.getSort())
|
client.listBy(Post.class, listOptions, query.toPageRequest())
|
||||||
))
|
)
|
||||||
.flatMap(listResult -> Flux.fromStream(listResult.get())
|
.flatMap(listResult -> Flux.fromStream(listResult.get())
|
||||||
.map(this::getListedPost)
|
.map(this::getListedPost)
|
||||||
.concatMap(Function.identity())
|
.concatMap(Function.identity())
|
||||||
|
|
|
@ -86,16 +86,15 @@ public class SinglePageServiceImpl extends AbstractContentService implements Sin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedSinglePage>> list(SinglePageQuery query) {
|
public Mono<ListResult<ListedSinglePage>> list(SinglePageQuery query) {
|
||||||
return client.list(SinglePage.class, query.toPredicate(),
|
return client.listBy(SinglePage.class, query.toListOptions(), query.toPageRequest())
|
||||||
query.toComparator(), query.getPage(), query.getSize())
|
.flatMap(listResult -> Flux.fromStream(listResult.get().map(this::getListedSinglePage))
|
||||||
.flatMap(listResult -> Flux.fromStream(
|
|
||||||
listResult.get().map(this::getListedSinglePage)
|
|
||||||
)
|
|
||||||
.concatMap(Function.identity())
|
.concatMap(Function.identity())
|
||||||
.collectList()
|
.collectList()
|
||||||
.map(listedSinglePages -> new ListResult<>(listResult.getPage(),
|
.map(listedSinglePages -> new ListResult<>(
|
||||||
|
listResult.getPage(),
|
||||||
listResult.getSize(),
|
listResult.getSize(),
|
||||||
listResult.getTotal(), listedSinglePages)
|
listResult.getTotal(),
|
||||||
|
listedSinglePages)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,26 +10,20 @@ import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder;
|
||||||
import static org.springframework.boot.convert.ApplicationConversionService.getSharedInstance;
|
import static org.springframework.boot.convert.ApplicationConversionService.getSharedInstance;
|
||||||
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
|
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
|
||||||
import static run.halo.app.extension.ListResult.generateGenericClass;
|
import static run.halo.app.extension.ListResult.generateGenericClass;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.all;
|
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.and;
|
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.contains;
|
import static run.halo.app.extension.index.query.QueryFactory.contains;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.in;
|
import static run.halo.app.extension.index.query.QueryFactory.in;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.isNull;
|
import static run.halo.app.extension.index.query.QueryFactory.isNull;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.not;
|
import static run.halo.app.extension.index.query.QueryFactory.not;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.startsWith;
|
import static run.halo.app.extension.index.query.QueryFactory.startsWith;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
|
||||||
import org.springdoc.core.fn.builders.operation.Builder;
|
import org.springdoc.core.fn.builders.operation.Builder;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
|
@ -45,21 +39,19 @@ import org.springframework.web.reactive.function.BodyExtractors;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import org.springframework.web.server.ServerWebInputException;
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.attachment.Attachment;
|
import run.halo.app.core.extension.attachment.Attachment;
|
||||||
import run.halo.app.core.extension.attachment.Group;
|
import run.halo.app.core.extension.attachment.Group;
|
||||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||||
import run.halo.app.core.extension.endpoint.SortResolver;
|
|
||||||
import run.halo.app.core.extension.service.AttachmentService;
|
import run.halo.app.core.extension.service.AttachmentService;
|
||||||
import run.halo.app.extension.ListOptions;
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.PageRequestImpl;
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.index.query.QueryFactory;
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.IListRequest;
|
||||||
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
|
||||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||||
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
import run.halo.app.extension.router.selector.LabelSelector;
|
import run.halo.app.extension.router.selector.LabelSelector;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -131,7 +123,7 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
||||||
.response(
|
.response(
|
||||||
responseBuilder().implementation(generateGenericClass(Attachment.class))
|
responseBuilder().implementation(generateGenericClass(Attachment.class))
|
||||||
);
|
);
|
||||||
ISearchRequest.buildParameters(builder);
|
SearchRequest.buildParameters(builder);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
@ -159,33 +151,58 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ISearchRequest extends IListRequest {
|
public static class SearchRequest extends SortableRequest {
|
||||||
|
|
||||||
@Schema(description = "Keyword for searching.")
|
public SearchRequest(ServerRequest request) {
|
||||||
Optional<String> getKeyword();
|
super(request.exchange());
|
||||||
|
}
|
||||||
|
|
||||||
@Schema(description = "Filter attachments without group. This parameter will ignore group"
|
public Optional<String> getKeyword() {
|
||||||
+ " parameter.")
|
return Optional.ofNullable(queryParams.getFirst("keyword"))
|
||||||
Optional<Boolean> getUngrouped();
|
.filter(StringUtils::hasText);
|
||||||
|
}
|
||||||
|
|
||||||
@ArraySchema(uniqueItems = true,
|
public Optional<Boolean> getUngrouped() {
|
||||||
arraySchema = @Schema(name = "accepts",
|
return Optional.ofNullable(queryParams.getFirst("ungrouped"))
|
||||||
description = "Acceptable media types."),
|
.map(ungroupedStr -> getSharedInstance().convert(ungroupedStr, Boolean.class));
|
||||||
schema = @Schema(description = "like image/*, video/mp4, text/*",
|
}
|
||||||
implementation = String.class,
|
|
||||||
example = "image/*"))
|
|
||||||
List<String> getAccepts();
|
|
||||||
|
|
||||||
@ArraySchema(uniqueItems = true,
|
public Optional<List<String>> getAccepts() {
|
||||||
arraySchema = @Schema(name = "sort",
|
return Optional.ofNullable(queryParams.get("accepts"))
|
||||||
description = "Sort property and direction of the list result. Supported fields: "
|
.filter(accepts -> !accepts.isEmpty()
|
||||||
+ "creationTimestamp, size"),
|
&& !accepts.contains("*")
|
||||||
schema = @Schema(description = "like field,asc or field,desc",
|
&& !accepts.contains("*/*")
|
||||||
implementation = String.class,
|
);
|
||||||
example = "creationTimestamp,desc"))
|
}
|
||||||
Sort getSort();
|
|
||||||
|
|
||||||
static void buildParameters(Builder builder) {
|
public ListOptions toListOptions(List<String> hiddenGroups) {
|
||||||
|
var builder = ListOptions.builder(super.toListOptions());
|
||||||
|
|
||||||
|
getKeyword().ifPresent(keyword -> {
|
||||||
|
builder.andQuery(contains("spec.displayName", keyword));
|
||||||
|
});
|
||||||
|
|
||||||
|
getUngrouped()
|
||||||
|
.filter(ungrouped -> ungrouped)
|
||||||
|
.ifPresent(ungrouped -> builder.andQuery(isNull("spec.groupName")));
|
||||||
|
|
||||||
|
if (!CollectionUtils.isEmpty(hiddenGroups)) {
|
||||||
|
builder.andQuery(not(in("spec.groupName", hiddenGroups)));
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccepts().flatMap(accepts -> accepts.stream()
|
||||||
|
.filter(StringUtils::hasText)
|
||||||
|
.map(accept -> accept.replace("/*", "/").toLowerCase())
|
||||||
|
.distinct()
|
||||||
|
.map(accept -> startsWith("spec.mediaType", accept))
|
||||||
|
.reduce(QueryFactory::or)
|
||||||
|
)
|
||||||
|
.ifPresent(builder::andQuery);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void buildParameters(Builder builder) {
|
||||||
IListRequest.buildParameters(builder);
|
IListRequest.buildParameters(builder);
|
||||||
builder.parameter(QueryParamBuildUtil.sortParameter())
|
builder.parameter(QueryParamBuildUtil.sortParameter())
|
||||||
.parameter(parameterBuilder()
|
.parameter(parameterBuilder()
|
||||||
|
@ -220,82 +237,6 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SearchRequest extends QueryListRequest implements ISearchRequest {
|
|
||||||
|
|
||||||
private final ServerWebExchange exchange;
|
|
||||||
|
|
||||||
public SearchRequest(ServerRequest request) {
|
|
||||||
super(request.queryParams());
|
|
||||||
this.exchange = request.exchange();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<String> getKeyword() {
|
|
||||||
return Optional.ofNullable(queryParams.getFirst("keyword"))
|
|
||||||
.filter(StringUtils::hasText);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Boolean> getUngrouped() {
|
|
||||||
return Optional.ofNullable(queryParams.getFirst("ungrouped"))
|
|
||||||
.map(ungroupedStr -> getSharedInstance().convert(ungroupedStr, Boolean.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getAccepts() {
|
|
||||||
return queryParams.getOrDefault("accepts", Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Sort getSort() {
|
|
||||||
var sort = SortResolver.defaultInstance.resolve(exchange);
|
|
||||||
sort = sort.and(Sort.by(
|
|
||||||
Sort.Order.desc("metadata.creationTimestamp"),
|
|
||||||
Sort.Order.asc("metadata.name")
|
|
||||||
));
|
|
||||||
return sort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListOptions toListOptions(List<String> hiddenGroups) {
|
|
||||||
final var listOptions =
|
|
||||||
labelAndFieldSelectorToListOptions(getLabelSelector(), getFieldSelector());
|
|
||||||
|
|
||||||
var fieldQuery = all();
|
|
||||||
if (getKeyword().isPresent()) {
|
|
||||||
fieldQuery = and(fieldQuery, contains("spec.displayName", getKeyword().get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getUngrouped().isPresent() && BooleanUtils.isTrue(getUngrouped().get())) {
|
|
||||||
fieldQuery = and(fieldQuery, isNull("spec.groupName"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hiddenGroups.isEmpty()) {
|
|
||||||
fieldQuery = and(fieldQuery, not(in("spec.groupName", hiddenGroups)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAccepts()) {
|
|
||||||
var acceptFieldQueryOptional = getAccepts().stream()
|
|
||||||
.filter(StringUtils::hasText)
|
|
||||||
.map((accept -> accept.replace("/*", "/").toLowerCase()))
|
|
||||||
.distinct()
|
|
||||||
.map(accept -> startsWith("spec.mediaType", accept))
|
|
||||||
.reduce(QueryFactory::or);
|
|
||||||
if (acceptFieldQueryOptional.isPresent()) {
|
|
||||||
fieldQuery = and(fieldQuery, acceptFieldQueryOptional.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listOptions.setFieldSelector(listOptions.getFieldSelector().andQuery(fieldQuery));
|
|
||||||
return listOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasAccepts() {
|
|
||||||
return !CollectionUtils.isEmpty(getAccepts())
|
|
||||||
&& !getAccepts().contains("*")
|
|
||||||
&& !getAccepts().contains("*/*");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record UploadFromUrlRequest(@Schema(requiredMode = REQUIRED) URL url,
|
public record UploadFromUrlRequest(@Schema(requiredMode = REQUIRED) URL url,
|
||||||
@Schema(requiredMode = REQUIRED) String policyName,
|
@Schema(requiredMode = REQUIRED) String policyName,
|
||||||
String groupName,
|
String groupName,
|
||||||
|
|
|
@ -2,7 +2,6 @@ package run.halo.app.core.extension.endpoint;
|
||||||
|
|
||||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
|
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
|
||||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||||
import static java.util.Comparator.comparing;
|
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
|
@ -12,12 +11,13 @@ import static org.springframework.boot.convert.ApplicationConversionService.getS
|
||||||
import static org.springframework.core.io.buffer.DataBufferUtils.write;
|
import static org.springframework.core.io.buffer.DataBufferUtils.write;
|
||||||
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
|
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
|
||||||
import static run.halo.app.extension.ListResult.generateGenericClass;
|
import static run.halo.app.extension.ListResult.generateGenericClass;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.contains;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.or;
|
||||||
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
|
||||||
import static run.halo.app.infra.utils.FileUtils.deleteFileSilently;
|
import static run.halo.app.infra.utils.FileUtils.deleteFileSilently;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -27,13 +27,9 @@ import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
|
@ -57,7 +53,6 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.reactive.resource.NoResourceFoundException;
|
import org.springframework.web.reactive.resource.NoResourceFoundException;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import org.springframework.web.server.ServerWebInputException;
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Scheduler;
|
import reactor.core.scheduler.Scheduler;
|
||||||
|
@ -67,11 +62,11 @@ import run.halo.app.core.extension.Plugin;
|
||||||
import run.halo.app.core.extension.Setting;
|
import run.halo.app.core.extension.Setting;
|
||||||
import run.halo.app.core.extension.service.PluginService;
|
import run.halo.app.core.extension.service.PluginService;
|
||||||
import run.halo.app.core.extension.theme.SettingUtils;
|
import run.halo.app.core.extension.theme.SettingUtils;
|
||||||
import run.halo.app.extension.Comparators;
|
|
||||||
import run.halo.app.extension.ConfigMap;
|
import run.halo.app.extension.ConfigMap;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.IListRequest;
|
||||||
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
import run.halo.app.infra.ReactiveUrlDataBufferFetcher;
|
import run.halo.app.infra.ReactiveUrlDataBufferFetcher;
|
||||||
import run.halo.app.plugin.PluginNotFoundException;
|
import run.halo.app.plugin.PluginNotFoundException;
|
||||||
|
|
||||||
|
@ -548,13 +543,10 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
|
||||||
.flatMap(resourceClosure);
|
.flatMap(resourceClosure);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ListRequest extends QueryListRequest {
|
public static class ListRequest extends SortableRequest {
|
||||||
|
|
||||||
private final ServerWebExchange exchange;
|
|
||||||
|
|
||||||
public ListRequest(ServerRequest request) {
|
public ListRequest(ServerRequest request) {
|
||||||
super(request.queryParams());
|
super(request.exchange());
|
||||||
this.exchange = request.exchange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(name = "keyword", description = "Keyword of plugin name or description")
|
@Schema(name = "keyword", description = "Keyword of plugin name or description")
|
||||||
|
@ -568,69 +560,35 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
|
||||||
return enabled == null ? null : getSharedInstance().convert(enabled, Boolean.class);
|
return enabled == null ? null : getSharedInstance().convert(enabled, Boolean.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ArraySchema(uniqueItems = true,
|
@Override
|
||||||
arraySchema = @Schema(name = "sort",
|
|
||||||
description = "Sort property and direction of the list result. Supported fields: "
|
|
||||||
+ "creationTimestamp"),
|
|
||||||
schema = @Schema(description = "like field,asc or field,desc",
|
|
||||||
implementation = String.class,
|
|
||||||
example = "creationTimestamp,desc"))
|
|
||||||
public Sort getSort() {
|
public Sort getSort() {
|
||||||
return SortResolver.defaultInstance.resolve(exchange);
|
var orders = super.getSort().stream()
|
||||||
|
.map(order -> {
|
||||||
|
if ("creationTimestamp".equals(order.getProperty())) {
|
||||||
|
return order.withProperty("metadata.creationTimestamp");
|
||||||
|
}
|
||||||
|
return order;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
return Sort.by(orders);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Predicate<Plugin> toPredicate() {
|
@Override
|
||||||
Predicate<Plugin> displayNamePredicate = plugin -> {
|
public ListOptions toListOptions() {
|
||||||
var keyword = getKeyword();
|
var builder = ListOptions.builder(super.toListOptions());
|
||||||
if (!StringUtils.hasText(keyword)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
var displayName = plugin.getSpec().getDisplayName();
|
|
||||||
if (!StringUtils.hasText(displayName)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return displayName.toLowerCase().contains(keyword.trim().toLowerCase());
|
|
||||||
};
|
|
||||||
Predicate<Plugin> descriptionPredicate = plugin -> {
|
|
||||||
var keyword = getKeyword();
|
|
||||||
if (!StringUtils.hasText(keyword)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
var description = plugin.getSpec().getDescription();
|
|
||||||
if (!StringUtils.hasText(description)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return description.toLowerCase().contains(keyword.trim().toLowerCase());
|
|
||||||
};
|
|
||||||
Predicate<Plugin> enablePredicate = plugin -> {
|
|
||||||
var enabled = getEnabled();
|
|
||||||
if (enabled == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return Objects.equals(enabled, plugin.getSpec().getEnabled());
|
|
||||||
};
|
|
||||||
return displayNamePredicate.or(descriptionPredicate)
|
|
||||||
.and(enablePredicate)
|
|
||||||
.and(labelAndFieldSelectorToPredicate(getLabelSelector(), getFieldSelector()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Comparator<Plugin> toComparator() {
|
Optional.ofNullable(queryParams.getFirst("keyword"))
|
||||||
var sort = getSort();
|
.filter(StringUtils::hasText)
|
||||||
var ctOrder = sort.getOrderFor("creationTimestamp");
|
.ifPresent(keyword -> builder.andQuery(or(
|
||||||
List<Comparator<Plugin>> comparators = new ArrayList<>();
|
contains("spec.displayName", keyword),
|
||||||
if (ctOrder != null) {
|
contains("spec.description", keyword)
|
||||||
Comparator<Plugin> comparator =
|
)));
|
||||||
comparing(plugin -> plugin.getMetadata().getCreationTimestamp());
|
|
||||||
if (ctOrder.isDescending()) {
|
Optional.ofNullable(queryParams.getFirst("enabled"))
|
||||||
comparator = comparator.reversed();
|
.map(Boolean::parseBoolean)
|
||||||
}
|
.ifPresent(enabled -> builder.andQuery(equal("spec.enabled", enabled.toString())));
|
||||||
comparators.add(comparator);
|
|
||||||
}
|
return builder.build();
|
||||||
comparators.add(Comparators.compareCreationTimestamp(false));
|
|
||||||
comparators.add(Comparators.compareName(true));
|
|
||||||
return comparators.stream()
|
|
||||||
.reduce(Comparator::thenComparing)
|
|
||||||
.orElse(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void buildParameters(Builder builder) {
|
public static void buildParameters(Builder builder) {
|
||||||
|
@ -654,15 +612,11 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
|
||||||
Mono<ServerResponse> list(ServerRequest request) {
|
Mono<ServerResponse> list(ServerRequest request) {
|
||||||
return Mono.just(request)
|
return Mono.just(request)
|
||||||
.map(ListRequest::new)
|
.map(ListRequest::new)
|
||||||
.flatMap(listRequest -> {
|
.flatMap(listRequest -> client.listBy(
|
||||||
var predicate = listRequest.toPredicate();
|
Plugin.class,
|
||||||
var comparator = listRequest.toComparator();
|
listRequest.toListOptions(),
|
||||||
return client.list(Plugin.class,
|
listRequest.toPageRequest()
|
||||||
predicate,
|
))
|
||||||
comparator,
|
|
||||||
listRequest.getPage(),
|
|
||||||
listRequest.getSize());
|
|
||||||
})
|
|
||||||
.flatMap(listResult -> ServerResponse.ok().bodyValue(listResult));
|
.flatMap(listResult -> ServerResponse.ok().bodyValue(listResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -403,7 +403,7 @@ public class PostEndpoint implements CustomEndpoint {
|
||||||
/**
|
/**
|
||||||
* Convenient for testing, to avoid waiting too long for post published when testing.
|
* Convenient for testing, to avoid waiting too long for post published when testing.
|
||||||
*/
|
*/
|
||||||
public void setMaxAttemptsWaitForPublish(int maxAttempts) {
|
void setMaxAttemptsWaitForPublish(int maxAttempts) {
|
||||||
this.maxAttemptsWaitForPublish = maxAttempts;
|
this.maxAttemptsWaitForPublish = maxAttempts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,33 +2,29 @@ package run.halo.app.core.extension.endpoint;
|
||||||
|
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.all;
|
import static run.halo.app.extension.index.query.QueryFactory.contains;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.or;
|
||||||
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springdoc.core.fn.builders.operation.Builder;
|
import org.springdoc.core.fn.builders.operation.Builder;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
import run.halo.app.extension.ListOptions;
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.PageRequestImpl;
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.index.query.QueryFactory;
|
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.IListRequest;
|
||||||
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* post tag endpoint.
|
* post tag endpoint.
|
||||||
|
@ -68,61 +64,27 @@ public class TagEndpoint implements CustomEndpoint {
|
||||||
.flatMap(tags -> ServerResponse.ok().bodyValue(tags));
|
.flatMap(tags -> ServerResponse.ok().bodyValue(tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ITagQuery extends IListRequest {
|
public static class TagQuery extends SortableRequest {
|
||||||
|
|
||||||
@Schema(description = "Keyword for searching.")
|
|
||||||
Optional<String> getKeyword();
|
|
||||||
|
|
||||||
@ArraySchema(uniqueItems = true,
|
|
||||||
arraySchema = @Schema(name = "sort",
|
|
||||||
description = "Sort property and direction of the list result. Supported fields: "
|
|
||||||
+ "creationTimestamp, name"),
|
|
||||||
schema = @Schema(description = "like field,asc or field,desc",
|
|
||||||
implementation = String.class,
|
|
||||||
example = "creationTimestamp,desc"))
|
|
||||||
Sort getSort();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TagQuery extends IListRequest.QueryListRequest
|
|
||||||
implements ITagQuery {
|
|
||||||
|
|
||||||
private final ServerWebExchange exchange;
|
|
||||||
|
|
||||||
public TagQuery(ServerRequest request) {
|
public TagQuery(ServerRequest request) {
|
||||||
super(request.queryParams());
|
super(request.exchange());
|
||||||
this.exchange = request.exchange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<String> getKeyword() {
|
public Optional<String> getKeyword() {
|
||||||
return Optional.ofNullable(queryParams.getFirst("keyword"))
|
return Optional.ofNullable(queryParams.getFirst("keyword"))
|
||||||
.filter(StringUtils::hasText);
|
.filter(StringUtils::hasText);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Sort getSort() {
|
|
||||||
var sort = SortResolver.defaultInstance.resolve(exchange);
|
|
||||||
sort = sort.and(Sort.by(
|
|
||||||
Sort.Order.desc("metadata.creationTimestamp"),
|
|
||||||
Sort.Order.asc("metadata.name")
|
|
||||||
));
|
|
||||||
return sort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListOptions toListOptions() {
|
public ListOptions toListOptions() {
|
||||||
final var listOptions =
|
var builder = ListOptions.builder(super.toListOptions());
|
||||||
labelAndFieldSelectorToListOptions(getLabelSelector(), getFieldSelector());
|
getKeyword().ifPresent(keyword -> builder.andQuery(
|
||||||
|
or(
|
||||||
var fieldQuery = all();
|
contains("spec.displayName", keyword),
|
||||||
if (getKeyword().isPresent()) {
|
contains("spec.slug", keyword)
|
||||||
fieldQuery = QueryFactory.and(fieldQuery, QueryFactory.or(
|
)
|
||||||
QueryFactory.contains("spec.displayName", getKeyword().get()),
|
|
||||||
QueryFactory.contains("spec.slug", getKeyword().get())
|
|
||||||
));
|
));
|
||||||
}
|
return builder.build();
|
||||||
|
|
||||||
listOptions.setFieldSelector(listOptions.getFieldSelector().andQuery(fieldQuery));
|
|
||||||
return listOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void buildParameters(Builder builder) {
|
public static void buildParameters(Builder builder) {
|
||||||
|
|
|
@ -14,7 +14,6 @@ import static run.halo.app.extension.index.query.QueryFactory.contains;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.in;
|
import static run.halo.app.extension.index.query.QueryFactory.in;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.or;
|
import static run.halo.app.extension.index.query.QueryFactory.or;
|
||||||
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
||||||
import static run.halo.app.security.authorization.AuthorityUtils.authoritiesToRoles;
|
import static run.halo.app.security.authorization.AuthorityUtils.authoritiesToRoles;
|
||||||
|
|
||||||
|
@ -24,7 +23,6 @@ import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
|
||||||
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
|
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
|
||||||
import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator;
|
import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator;
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
@ -48,7 +46,6 @@ import org.springdoc.core.fn.builders.operation.Builder;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
import org.springframework.dao.OptimisticLockingFailureException;
|
import org.springframework.dao.OptimisticLockingFailureException;
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.multipart.FilePart;
|
import org.springframework.http.codec.multipart.FilePart;
|
||||||
import org.springframework.http.codec.multipart.Part;
|
import org.springframework.http.codec.multipart.Part;
|
||||||
|
@ -65,7 +62,6 @@ import org.springframework.web.reactive.function.BodyExtractors;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import org.springframework.web.server.ServerWebInputException;
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -84,7 +80,7 @@ import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.MetadataUtil;
|
import run.halo.app.extension.MetadataUtil;
|
||||||
import run.halo.app.extension.PageRequestImpl;
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.ValidationUtils;
|
import run.halo.app.infra.ValidationUtils;
|
||||||
|
@ -687,13 +683,10 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ListRequest extends IListRequest.QueryListRequest {
|
public static class ListRequest extends SortableRequest {
|
||||||
|
|
||||||
private final ServerWebExchange exchange;
|
|
||||||
|
|
||||||
public ListRequest(ServerRequest request) {
|
public ListRequest(ServerRequest request) {
|
||||||
super(request.queryParams());
|
super(request.exchange());
|
||||||
this.exchange = request.exchange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(name = "keyword")
|
@Schema(name = "keyword")
|
||||||
|
@ -706,19 +699,6 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
return queryParams.getFirst("role");
|
return queryParams.getFirst("role");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ArraySchema(uniqueItems = true,
|
|
||||||
arraySchema = @Schema(name = "sort",
|
|
||||||
description = "Sort property and direction of the list result. Supported fields: "
|
|
||||||
+ "creationTimestamp"),
|
|
||||||
schema = @Schema(description = "like field,asc or field,desc",
|
|
||||||
implementation = String.class,
|
|
||||||
example = "creationTimestamp,desc"))
|
|
||||||
public Sort getSort() {
|
|
||||||
var sort = SortResolver.defaultInstance.resolve(exchange);
|
|
||||||
sort = sort.and(Sort.by("metadata.creationTimestamp", "metadata.name").descending());
|
|
||||||
return sort;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts query parameters to list options.
|
* Converts query parameters to list options.
|
||||||
*/
|
*/
|
||||||
|
@ -743,9 +723,8 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void buildParameters(Builder builder) {
|
public static void buildParameters(Builder builder) {
|
||||||
IListRequest.buildParameters(builder);
|
SortableRequest.buildParameters(builder);
|
||||||
builder.parameter(sortParameter())
|
builder.parameter(parameterBuilder()
|
||||||
.parameter(parameterBuilder()
|
|
||||||
.in(ParameterIn.QUERY)
|
.in(ParameterIn.QUERY)
|
||||||
.name("keyword")
|
.name("keyword")
|
||||||
.description("Keyword to search")
|
.description("Keyword to search")
|
||||||
|
@ -770,8 +749,7 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
.map(UserEndpoint.ListRequest::new)
|
.map(UserEndpoint.ListRequest::new)
|
||||||
.flatMap(listRequest -> client.listBy(User.class, listRequest.toListOptions(),
|
.flatMap(listRequest -> client.listBy(User.class, listRequest.toListOptions(),
|
||||||
PageRequestImpl.of(
|
PageRequestImpl.of(
|
||||||
listRequest.getPage(), listRequest.getSize(),
|
listRequest.getPage(), listRequest.getSize(), listRequest.getSort()
|
||||||
listRequest.getSort()
|
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
.flatMap(this::toListedUser)
|
.flatMap(this::toListedUser)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import static run.halo.app.extension.index.IndexAttributeFactory.multiValueAttri
|
||||||
import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute;
|
import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -95,7 +96,33 @@ public class SchemeInitializer implements ApplicationListener<ApplicationContext
|
||||||
});
|
});
|
||||||
|
|
||||||
// plugin.halo.run
|
// plugin.halo.run
|
||||||
schemeManager.register(Plugin.class);
|
schemeManager.register(Plugin.class, is -> {
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.displayName")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(Plugin.class, plugin -> Optional.ofNullable(plugin.getSpec())
|
||||||
|
.map(Plugin.PluginSpec::getDisplayName)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.description")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(Plugin.class, plugin -> Optional.ofNullable(plugin.getSpec())
|
||||||
|
.map(Plugin.PluginSpec::getDescription)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.enabled")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(Plugin.class, plugin -> Optional.ofNullable(plugin.getSpec())
|
||||||
|
.map(Plugin.PluginSpec::getEnabled)
|
||||||
|
.map(Object::toString)
|
||||||
|
.orElse(Boolean.FALSE.toString()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
schemeManager.register(SearchEngine.class);
|
schemeManager.register(SearchEngine.class);
|
||||||
schemeManager.register(ExtensionPointDefinition.class, indexSpecs -> {
|
schemeManager.register(ExtensionPointDefinition.class, indexSpecs -> {
|
||||||
indexSpecs.add(new IndexSpec()
|
indexSpecs.add(new IndexSpec()
|
||||||
|
@ -443,7 +470,85 @@ public class SchemeInitializer implements ApplicationListener<ApplicationContext
|
||||||
return null;
|
return null;
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
schemeManager.register(SinglePage.class);
|
schemeManager.register(SinglePage.class, is -> {
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.publishTime")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(SinglePage.class, page -> Optional.ofNullable(page.getSpec())
|
||||||
|
.map(SinglePage.SinglePageSpec::getPublishTime)
|
||||||
|
.map(Instant::toString)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.title")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(SinglePage.class, page -> Optional.ofNullable(page.getSpec())
|
||||||
|
.map(SinglePage.SinglePageSpec::getTitle)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.slug")
|
||||||
|
.setUnique(true)
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(SinglePage.class, page -> Optional.ofNullable(page.getSpec())
|
||||||
|
.map(SinglePage.SinglePageSpec::getSlug)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.visible")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(SinglePage.class, page -> Optional.ofNullable(page.getSpec())
|
||||||
|
.map(SinglePage.SinglePageSpec::getVisible)
|
||||||
|
.map(Post.VisibleEnum::name)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.pinned")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(SinglePage.class, page -> Optional.ofNullable(page.getSpec())
|
||||||
|
.map(SinglePage.SinglePageSpec::getPinned)
|
||||||
|
.map(Object::toString)
|
||||||
|
.orElse(Boolean.FALSE.toString())))
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("spec.priority")
|
||||||
|
.setIndexFunc(simpleAttribute(SinglePage.class,
|
||||||
|
page -> Optional.ofNullable(page.getSpec())
|
||||||
|
.map(SinglePage.SinglePageSpec::getPriority)
|
||||||
|
.map(Object::toString)
|
||||||
|
.orElse(Integer.toString(0)))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("status.excerpt")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(SinglePage.class, page -> Optional.ofNullable(page.getStatus())
|
||||||
|
.map(SinglePage.SinglePageStatus::getExcerpt)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("status.phase")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(SinglePage.class, page -> Optional.ofNullable(page.getStatus())
|
||||||
|
.map(SinglePage.SinglePageStatus::getPhase)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
is.add(new IndexSpec()
|
||||||
|
.setName("status.contributors")
|
||||||
|
.setIndexFunc(multiValueAttribute(SinglePage.class,
|
||||||
|
page -> Optional.ofNullable(page.getStatus())
|
||||||
|
.map(SinglePage.SinglePageStatus::getContributors)
|
||||||
|
.map(Set::copyOf)
|
||||||
|
.orElse(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
// storage.halo.run
|
// storage.halo.run
|
||||||
schemeManager.register(Group.class);
|
schemeManager.register(Group.class);
|
||||||
schemeManager.register(Policy.class);
|
schemeManager.register(Policy.class);
|
||||||
|
|
|
@ -19,6 +19,7 @@ import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||||
import run.halo.app.extension.GroupVersion;
|
import run.halo.app.extension.GroupVersion;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.router.SortableRequest;
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
|
import run.halo.app.theme.finders.SinglePageConversionService;
|
||||||
import run.halo.app.theme.finders.SinglePageFinder;
|
import run.halo.app.theme.finders.SinglePageFinder;
|
||||||
import run.halo.app.theme.finders.vo.ListedSinglePageVo;
|
import run.halo.app.theme.finders.vo.ListedSinglePageVo;
|
||||||
import run.halo.app.theme.finders.vo.SinglePageVo;
|
import run.halo.app.theme.finders.vo.SinglePageVo;
|
||||||
|
@ -35,6 +36,8 @@ public class SinglePageQueryEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
private final SinglePageFinder singlePageFinder;
|
private final SinglePageFinder singlePageFinder;
|
||||||
|
|
||||||
|
private final SinglePageConversionService singlePageConversionService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> endpoint() {
|
public RouterFunction<ServerResponse> endpoint() {
|
||||||
var tag = "SinglePageV1alpha1Public";
|
var tag = "SinglePageV1alpha1Public";
|
||||||
|
@ -79,15 +82,8 @@ public class SinglePageQueryEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
private Mono<ServerResponse> listSinglePages(ServerRequest request) {
|
private Mono<ServerResponse> listSinglePages(ServerRequest request) {
|
||||||
var query = new SinglePagePublicQuery(request.exchange());
|
var query = new SinglePagePublicQuery(request.exchange());
|
||||||
return singlePageFinder.list(query.getPage(),
|
return singlePageConversionService.listBy(query.toListOptions(), query.toPageRequest())
|
||||||
query.getSize(),
|
.flatMap(result -> ServerResponse.ok().bodyValue(result));
|
||||||
query.toPredicate(),
|
|
||||||
query.toComparator()
|
|
||||||
)
|
|
||||||
.flatMap(result -> ServerResponse.ok()
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.bodyValue(result)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SinglePagePublicQuery extends SortableRequest {
|
static class SinglePagePublicQuery extends SortableRequest {
|
||||||
|
|
|
@ -3,6 +3,9 @@ package run.halo.app.theme.finders;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.theme.ReactiveSinglePageContentHandler;
|
import run.halo.app.theme.ReactiveSinglePageContentHandler;
|
||||||
import run.halo.app.theme.finders.vo.ContentVo;
|
import run.halo.app.theme.finders.vo.ContentVo;
|
||||||
import run.halo.app.theme.finders.vo.ListedSinglePageVo;
|
import run.halo.app.theme.finders.vo.ListedSinglePageVo;
|
||||||
|
@ -52,4 +55,7 @@ public interface SinglePageConversionService {
|
||||||
Mono<ContentVo> getContent(String pageName);
|
Mono<ContentVo> getContent(String pageName);
|
||||||
|
|
||||||
Mono<ListedSinglePageVo> convertToListedVo(SinglePage singlePage);
|
Mono<ListedSinglePageVo> convertToListedVo(SinglePage singlePage);
|
||||||
|
|
||||||
|
Mono<ListResult<ListedSinglePageVo>> listBy(ListOptions listOptions, PageRequest pageRequest);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package run.halo.app.theme.finders;
|
package run.halo.app.theme.finders;
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
|
@ -24,6 +22,4 @@ public interface SinglePageFinder {
|
||||||
|
|
||||||
Mono<ListResult<ListedSinglePageVo>> list(@Nullable Integer page, @Nullable Integer size);
|
Mono<ListResult<ListedSinglePageVo>> list(@Nullable Integer page, @Nullable Integer size);
|
||||||
|
|
||||||
Mono<ListResult<ListedSinglePageVo>> list(@Nullable Integer page, @Nullable Integer size,
|
|
||||||
@Nullable Predicate<SinglePage> predicate, @Nullable Comparator<SinglePage> comparator);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,29 @@
|
||||||
package run.halo.app.theme.finders.impl;
|
package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
|
import static org.springframework.data.domain.Sort.Order.asc;
|
||||||
|
import static org.springframework.data.domain.Sort.Order.desc;
|
||||||
|
import static run.halo.app.core.extension.content.Post.VisibleEnum.PUBLIC;
|
||||||
|
import static run.halo.app.core.extension.content.SinglePage.PUBLISHED_LABEL;
|
||||||
|
import static run.halo.app.extension.ExtensionUtil.notDeleting;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.content.ContentWrapper;
|
import run.halo.app.content.ContentWrapper;
|
||||||
import run.halo.app.content.SinglePageService;
|
import run.halo.app.content.SinglePageService;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.metrics.CounterService;
|
import run.halo.app.metrics.CounterService;
|
||||||
import run.halo.app.metrics.MeterUtils;
|
import run.halo.app.metrics.MeterUtils;
|
||||||
|
@ -102,6 +115,40 @@ public class SinglePageConversionServiceImpl implements SinglePageConversionServ
|
||||||
.flatMap(this::populateContributors);
|
.flatMap(this::populateContributors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ListResult<ListedSinglePageVo>> listBy(ListOptions listOptions,
|
||||||
|
PageRequest pageRequest) {
|
||||||
|
// rewrite list options
|
||||||
|
var rewroteListOptions = ListOptions.builder(listOptions)
|
||||||
|
.andQuery(notDeleting())
|
||||||
|
.andQuery(equal("spec.deleted", Boolean.FALSE.toString()))
|
||||||
|
.andQuery(equal("spec.visible", PUBLIC.name()))
|
||||||
|
.labelSelector()
|
||||||
|
.eq(PUBLISHED_LABEL, Boolean.TRUE.toString())
|
||||||
|
.end()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// rewrite sort
|
||||||
|
var rewroteSort = pageRequest.getSort()
|
||||||
|
.and(Sort.by(
|
||||||
|
desc("spec.pinned"),
|
||||||
|
asc("spec.priority")
|
||||||
|
));
|
||||||
|
|
||||||
|
var rewrotePageRequest =
|
||||||
|
PageRequestImpl.of(pageRequest.getPageNumber(), pageRequest.getPageSize(), rewroteSort);
|
||||||
|
|
||||||
|
return client.listBy(SinglePage.class, rewroteListOptions, rewrotePageRequest)
|
||||||
|
.flatMap(list -> Flux.fromStream(list.get())
|
||||||
|
.concatMap(this::convertToListedVo)
|
||||||
|
.collectList()
|
||||||
|
.map(pageVos ->
|
||||||
|
new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), pageVos)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Mono<SinglePageVo> convert(SinglePage singlePage, String snapshotName) {
|
Mono<SinglePageVo> convert(SinglePage singlePage, String snapshotName) {
|
||||||
Assert.notNull(singlePage, "Single page must not be null");
|
Assert.notNull(singlePage, "Single page must not be null");
|
||||||
Assert.hasText(snapshotName, "Snapshot name must not be empty");
|
Assert.hasText(snapshotName, "Snapshot name must not be empty");
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
package run.halo.app.theme.finders.impl;
|
package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.infra.AnonymousUserConst;
|
import run.halo.app.infra.AnonymousUserConst;
|
||||||
import run.halo.app.theme.finders.Finder;
|
import run.halo.app.theme.finders.Finder;
|
||||||
|
@ -37,10 +31,6 @@ import run.halo.app.theme.finders.vo.SinglePageVo;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class SinglePageFinderImpl implements SinglePageFinder {
|
public class SinglePageFinderImpl implements SinglePageFinder {
|
||||||
|
|
||||||
public static final Predicate<SinglePage> FIXED_PREDICATE = page -> page.isPublished()
|
|
||||||
&& Objects.equals(false, page.getSpec().getDeleted())
|
|
||||||
&& Post.VisibleEnum.PUBLIC.equals(page.getSpec().getVisible());
|
|
||||||
|
|
||||||
private final ReactiveExtensionClient client;
|
private final ReactiveExtensionClient client;
|
||||||
|
|
||||||
private final SinglePageConversionService singlePagePublicQueryService;
|
private final SinglePageConversionService singlePagePublicQueryService;
|
||||||
|
@ -59,27 +49,10 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedSinglePageVo>> list(Integer page, Integer size) {
|
public Mono<ListResult<ListedSinglePageVo>> list(Integer page, Integer size) {
|
||||||
return list(page, size, null, null);
|
return singlePagePublicQueryService.listBy(
|
||||||
}
|
new ListOptions(),
|
||||||
|
PageRequestImpl.of(page, size)
|
||||||
@Override
|
);
|
||||||
public Mono<ListResult<ListedSinglePageVo>> list(@Nullable Integer page, @Nullable Integer size,
|
|
||||||
@Nullable Predicate<SinglePage> predicate, @Nullable Comparator<SinglePage> comparator) {
|
|
||||||
var predicateToUse = Optional.ofNullable(predicate)
|
|
||||||
.map(p -> p.and(FIXED_PREDICATE))
|
|
||||||
.orElse(FIXED_PREDICATE);
|
|
||||||
var comparatorToUse = Optional.ofNullable(comparator)
|
|
||||||
.orElse(defaultComparator());
|
|
||||||
return client.list(SinglePage.class, predicateToUse,
|
|
||||||
comparatorToUse, pageNullSafe(page), sizeNullSafe(size))
|
|
||||||
.flatMap(list -> Flux.fromStream(list.get())
|
|
||||||
.concatMap(singlePagePublicQueryService::convertToListedVo)
|
|
||||||
.collectList()
|
|
||||||
.map(pageVos -> new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
|
|
||||||
pageVos)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.defaultIfEmpty(new ListResult<>(0, 0, 0, List.of()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Mono<Predicate<SinglePage>> queryPredicate() {
|
Mono<Predicate<SinglePage>> queryPredicate() {
|
||||||
|
@ -101,26 +74,4 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
||||||
.filter(name -> !AnonymousUserConst.isAnonymousUser(name));
|
.filter(name -> !AnonymousUserConst.isAnonymousUser(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Comparator<SinglePage> defaultComparator() {
|
|
||||||
Function<SinglePage, Boolean> pinned =
|
|
||||||
page -> Objects.requireNonNullElse(page.getSpec().getPinned(), false);
|
|
||||||
Function<SinglePage, Integer> priority =
|
|
||||||
page -> Objects.requireNonNullElse(page.getSpec().getPriority(), 0);
|
|
||||||
Function<SinglePage, Instant> creationTimestamp =
|
|
||||||
page -> page.getMetadata().getCreationTimestamp();
|
|
||||||
Function<SinglePage, String> name = page -> page.getMetadata().getName();
|
|
||||||
return Comparator.comparing(pinned)
|
|
||||||
.thenComparing(priority)
|
|
||||||
.thenComparing(creationTimestamp)
|
|
||||||
.thenComparing(name)
|
|
||||||
.reversed();
|
|
||||||
}
|
|
||||||
|
|
||||||
int pageNullSafe(Integer page) {
|
|
||||||
return ObjectUtils.defaultIfNull(page, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sizeNullSafe(Integer size) {
|
|
||||||
return ObjectUtils.defaultIfNull(size, 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package run.halo.app.core.extension.endpoint;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isA;
|
import static org.mockito.ArgumentMatchers.isA;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
|
@ -23,9 +21,7 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
@ -50,8 +46,10 @@ import run.halo.app.core.extension.Plugin;
|
||||||
import run.halo.app.core.extension.Setting;
|
import run.halo.app.core.extension.Setting;
|
||||||
import run.halo.app.core.extension.service.PluginService;
|
import run.halo.app.core.extension.service.PluginService;
|
||||||
import run.halo.app.extension.ConfigMap;
|
import run.halo.app.extension.ConfigMap;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.infra.SystemVersionSupplier;
|
import run.halo.app.infra.SystemVersionSupplier;
|
||||||
import run.halo.app.infra.utils.FileUtils;
|
import run.halo.app.infra.utils.FileUtils;
|
||||||
|
@ -80,7 +78,7 @@ class PluginEndpointTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldListEmptyPluginsWhenNoPlugins() {
|
void shouldListEmptyPluginsWhenNoPlugins() {
|
||||||
when(client.list(same(Plugin.class), any(), any(), anyInt(), anyInt()))
|
when(client.listBy(same(Plugin.class), any(ListOptions.class), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(ListResult.emptyResult()));
|
.thenReturn(Mono.just(ListResult.emptyResult()));
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
bindToRouterFunction(endpoint.endpoint())
|
||||||
|
@ -101,7 +99,7 @@ class PluginEndpointTest {
|
||||||
createPlugin("fake-plugin-3")
|
createPlugin("fake-plugin-3")
|
||||||
);
|
);
|
||||||
var expectResult = new ListResult<>(plugins);
|
var expectResult = new ListResult<>(plugins);
|
||||||
when(client.list(same(Plugin.class), any(), any(), anyInt(), anyInt()))
|
when(client.listBy(same(Plugin.class), any(ListOptions.class), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(expectResult));
|
.thenReturn(Mono.just(expectResult));
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
bindToRouterFunction(endpoint.endpoint())
|
||||||
|
@ -126,7 +124,7 @@ class PluginEndpointTest {
|
||||||
expectPlugin
|
expectPlugin
|
||||||
);
|
);
|
||||||
var expectResult = new ListResult<>(plugins);
|
var expectResult = new ListResult<>(plugins);
|
||||||
when(client.list(same(Plugin.class), any(), any(), anyInt(), anyInt()))
|
when(client.listBy(same(Plugin.class), any(ListOptions.class), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(expectResult));
|
.thenReturn(Mono.just(expectResult));
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
bindToRouterFunction(endpoint.endpoint())
|
||||||
|
@ -134,27 +132,18 @@ class PluginEndpointTest {
|
||||||
.get().uri("/plugins?keyword=Expected")
|
.get().uri("/plugins?keyword=Expected")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk();
|
.expectStatus().isOk();
|
||||||
|
|
||||||
verify(client).list(same(Plugin.class), argThat(
|
|
||||||
predicate -> predicate.test(expectPlugin)
|
|
||||||
&& !predicate.test(unexpectedPlugin1)
|
|
||||||
&& !predicate.test(unexpectedPlugin2)),
|
|
||||||
any(), anyInt(), anyInt());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFilterPluginsWhenEnabledProvided() {
|
void shouldFilterPluginsWhenEnabledProvided() {
|
||||||
var expectPlugin =
|
var expectPlugin =
|
||||||
createPlugin("fake-plugin-2", "expected display name", "", true);
|
createPlugin("fake-plugin-2", "expected display name", "", true);
|
||||||
var unexpectedPlugin1 =
|
|
||||||
createPlugin("fake-plugin-1", "first fake display name", "", false);
|
|
||||||
var unexpectedPlugin2 =
|
|
||||||
createPlugin("fake-plugin-3", "second fake display name", "", false);
|
|
||||||
var plugins = List.of(
|
var plugins = List.of(
|
||||||
expectPlugin
|
expectPlugin
|
||||||
);
|
);
|
||||||
var expectResult = new ListResult<>(plugins);
|
var expectResult = new ListResult<>(plugins);
|
||||||
when(client.list(same(Plugin.class), any(), any(), anyInt(), anyInt()))
|
|
||||||
|
when(client.listBy(same(Plugin.class), any(ListOptions.class), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(expectResult));
|
.thenReturn(Mono.just(expectResult));
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
bindToRouterFunction(endpoint.endpoint())
|
||||||
|
@ -162,12 +151,6 @@ class PluginEndpointTest {
|
||||||
.get().uri("/plugins?enabled=true")
|
.get().uri("/plugins?enabled=true")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk();
|
.expectStatus().isOk();
|
||||||
|
|
||||||
verify(client).list(same(Plugin.class), argThat(
|
|
||||||
predicate -> predicate.test(expectPlugin)
|
|
||||||
&& !predicate.test(unexpectedPlugin1)
|
|
||||||
&& !predicate.test(unexpectedPlugin2)),
|
|
||||||
any(), anyInt(), anyInt());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -175,7 +158,7 @@ class PluginEndpointTest {
|
||||||
var expectPlugin =
|
var expectPlugin =
|
||||||
createPlugin("fake-plugin-2", "expected display name", "", true);
|
createPlugin("fake-plugin-2", "expected display name", "", true);
|
||||||
var expectResult = new ListResult<>(List.of(expectPlugin));
|
var expectResult = new ListResult<>(List.of(expectPlugin));
|
||||||
when(client.list(same(Plugin.class), any(), any(), anyInt(), anyInt()))
|
when(client.listBy(same(Plugin.class), any(ListOptions.class), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(expectResult));
|
.thenReturn(Mono.just(expectResult));
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
bindToRouterFunction(endpoint.endpoint())
|
||||||
|
@ -183,21 +166,6 @@ class PluginEndpointTest {
|
||||||
.get().uri("/plugins?sort=creationTimestamp,desc")
|
.get().uri("/plugins?sort=creationTimestamp,desc")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk();
|
.expectStatus().isOk();
|
||||||
|
|
||||||
verify(client).list(same(Plugin.class), any(), argThat(comparator -> {
|
|
||||||
var now = Instant.now();
|
|
||||||
var plugins = new ArrayList<>(List.of(
|
|
||||||
createPlugin("fake-plugin-a", now),
|
|
||||||
createPlugin("fake-plugin-b", now.plusSeconds(1)),
|
|
||||||
createPlugin("fake-plugin-c", now.plusSeconds(2))
|
|
||||||
));
|
|
||||||
plugins.sort(comparator);
|
|
||||||
return Objects.deepEquals(plugins, List.of(
|
|
||||||
createPlugin("fake-plugin-c", now.plusSeconds(2)),
|
|
||||||
createPlugin("fake-plugin-b", now.plusSeconds(1)),
|
|
||||||
createPlugin("fake-plugin-a", now)
|
|
||||||
));
|
|
||||||
}), anyInt(), anyInt());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,8 @@ import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
|
||||||
import static org.springframework.test.web.reactive.server.WebTestClient.bindToRouterFunction;
|
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||||
import static run.halo.app.extension.GroupVersionKind.fromExtension;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -24,19 +23,17 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.client.MultipartBodyBuilder;
|
import org.springframework.http.client.MultipartBodyBuilder;
|
||||||
import org.springframework.security.test.context.support.WithMockUser;
|
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.RoleBinding;
|
|
||||||
import run.halo.app.core.extension.User;
|
import run.halo.app.core.extension.User;
|
||||||
import run.halo.app.core.extension.attachment.Attachment;
|
import run.halo.app.core.extension.attachment.Attachment;
|
||||||
import run.halo.app.core.extension.service.AttachmentService;
|
import run.halo.app.core.extension.service.AttachmentService;
|
||||||
|
@ -46,14 +43,12 @@ import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.PageRequest;
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
|
||||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
|
import run.halo.app.infra.exception.UserNotFoundException;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
@SpringBootTest
|
@ExtendWith(MockitoExtension.class)
|
||||||
@AutoConfigureWebTestClient
|
|
||||||
@WithMockUser(username = "fake-user", password = "fake-password", roles = "fake-super-role")
|
|
||||||
class UserEndpointTest {
|
class UserEndpointTest {
|
||||||
|
|
||||||
WebTestClient webClient;
|
WebTestClient webClient;
|
||||||
|
@ -78,9 +73,10 @@ class UserEndpointTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
// disable authorization
|
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint())
|
||||||
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint()).build()
|
.apply(springSecurity())
|
||||||
.mutateWith(csrf());
|
.build()
|
||||||
|
.mutateWith(mockUser("fake-user").password("fake-password").roles("fake-super-role"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
@ -93,9 +89,7 @@ class UserEndpointTest {
|
||||||
when(client.listBy(same(User.class), any(), any(PageRequest.class)))
|
when(client.listBy(same(User.class), any(), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(ListResult.emptyResult()));
|
.thenReturn(Mono.just(ListResult.emptyResult()));
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
webClient.get().uri("/users")
|
||||||
.build()
|
|
||||||
.get().uri("/users")
|
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk()
|
.expectStatus().isOk()
|
||||||
.expectBody()
|
.expectBody()
|
||||||
|
@ -116,9 +110,7 @@ class UserEndpointTest {
|
||||||
when(client.listBy(same(User.class), any(), any(PageRequest.class)))
|
when(client.listBy(same(User.class), any(), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(expectResult));
|
.thenReturn(Mono.just(expectResult));
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
webClient.get().uri("/users")
|
||||||
.build()
|
|
||||||
.get().uri("/users")
|
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk()
|
.expectStatus().isOk()
|
||||||
.expectBody()
|
.expectBody()
|
||||||
|
@ -150,9 +142,7 @@ class UserEndpointTest {
|
||||||
when(roleService.getRolesByUsernames(any())).thenReturn(Mono.just(Map.of()));
|
when(roleService.getRolesByUsernames(any())).thenReturn(Mono.just(Map.of()));
|
||||||
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
webClient.get().uri("/users?role=guest")
|
||||||
.build()
|
|
||||||
.get().uri("/users?role=guest")
|
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk();
|
.expectStatus().isOk();
|
||||||
}
|
}
|
||||||
|
@ -167,9 +157,7 @@ class UserEndpointTest {
|
||||||
when(roleService.getRolesByUsernames(any())).thenReturn(Mono.just(Map.of()));
|
when(roleService.getRolesByUsernames(any())).thenReturn(Mono.just(Map.of()));
|
||||||
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
||||||
|
|
||||||
bindToRouterFunction(endpoint.endpoint())
|
webClient.get().uri("/users?sort=creationTimestamp,desc")
|
||||||
.build()
|
|
||||||
.get().uri("/users?sort=creationTimestamp,desc")
|
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk();
|
.expectStatus().isOk();
|
||||||
}
|
}
|
||||||
|
@ -190,16 +178,6 @@ class UserEndpointTest {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
User createUser(String name, Instant creationTimestamp) {
|
|
||||||
var metadata = new Metadata();
|
|
||||||
metadata.setName(name);
|
|
||||||
metadata.setCreationTimestamp(creationTimestamp);
|
|
||||||
var spec = new User.UserSpec();
|
|
||||||
var user = new User();
|
|
||||||
user.setMetadata(metadata);
|
|
||||||
user.setSpec(spec);
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
@ -209,8 +187,7 @@ class UserEndpointTest {
|
||||||
@Test
|
@Test
|
||||||
void shouldResponseErrorIfUserNotFound() {
|
void shouldResponseErrorIfUserNotFound() {
|
||||||
when(userService.getUser("fake-user"))
|
when(userService.getUser("fake-user"))
|
||||||
.thenReturn(Mono.error(
|
.thenReturn(Mono.error(new UserNotFoundException("fake-user")));
|
||||||
new ExtensionNotFoundException(fromExtension(User.class), "fake-user")));
|
|
||||||
webClient.get().uri("/users/-")
|
webClient.get().uri("/users/-")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isNotFound();
|
.expectStatus().isNotFound();
|
||||||
|
@ -236,7 +213,6 @@ class UserEndpointTest {
|
||||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||||
.expectBody(UserEndpoint.DetailedUser.class)
|
.expectBody(UserEndpoint.DetailedUser.class)
|
||||||
.isEqualTo(new UserEndpoint.DetailedUser(user, List.of(role)));
|
.isEqualTo(new UserEndpoint.DetailedUser(user, List.of(role)));
|
||||||
// verify(roleService).list(eq(Set.of("role-A")));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,11 +243,9 @@ class UserEndpointTest {
|
||||||
@Test
|
@Test
|
||||||
void shouldGetErrorIfUsernameMismatch() {
|
void shouldGetErrorIfUsernameMismatch() {
|
||||||
var currentUser = createUser("fake-user");
|
var currentUser = createUser("fake-user");
|
||||||
var updatedUser = createUser("fake-user");
|
|
||||||
var requestUser = createUser("another-fake-user");
|
var requestUser = createUser("another-fake-user");
|
||||||
|
|
||||||
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(currentUser));
|
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(currentUser));
|
||||||
when(client.update(currentUser)).thenReturn(Mono.just(updatedUser));
|
|
||||||
|
|
||||||
webClient.put().uri("/users/-")
|
webClient.put().uri("/users/-")
|
||||||
.bodyValue(requestUser)
|
.bodyValue(requestUser)
|
||||||
|
@ -324,8 +298,6 @@ class UserEndpointTest {
|
||||||
@Test
|
@Test
|
||||||
void shouldUpdateOtherPasswordCorrectly() {
|
void shouldUpdateOtherPasswordCorrectly() {
|
||||||
var user = new User();
|
var user = new User();
|
||||||
when(userService.confirmPassword("another-fake-user", "old-password"))
|
|
||||||
.thenReturn(Mono.just(true));
|
|
||||||
when(userService.updateWithRawPassword("another-fake-user", "new-password"))
|
when(userService.updateWithRawPassword("another-fake-user", "new-password"))
|
||||||
.thenReturn(Mono.just(user));
|
.thenReturn(Mono.just(user));
|
||||||
webClient.put()
|
webClient.put()
|
||||||
|
@ -347,14 +319,6 @@ class UserEndpointTest {
|
||||||
@DisplayName("GrantPermission")
|
@DisplayName("GrantPermission")
|
||||||
class GrantPermissionEndpointTest {
|
class GrantPermissionEndpointTest {
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
when(client.list(same(RoleBinding.class), any(), any())).thenReturn(Flux.empty());
|
|
||||||
when(client.get(User.class, "fake-user"))
|
|
||||||
.thenReturn(Mono.error(
|
|
||||||
new ExtensionNotFoundException(fromExtension(User.class), "fake-user")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldGetBadRequestIfRequestBodyIsEmpty() {
|
void shouldGetBadRequestIfRequestBodyIsEmpty() {
|
||||||
webClient.post().uri("/users/fake-user/permissions")
|
webClient.post().uri("/users/fake-user/permissions")
|
||||||
|
@ -395,7 +359,6 @@ class UserEndpointTest {
|
||||||
}
|
}
|
||||||
""", Role.class);
|
""", Role.class);
|
||||||
when(roleService.listPermissions(eq(Set.of("test-A")))).thenReturn(Flux.just(roleA));
|
when(roleService.listPermissions(eq(Set.of("test-A")))).thenReturn(Flux.just(roleA));
|
||||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(roleA));
|
|
||||||
when(roleService.getRolesByUsername("fake-user")).thenReturn(Flux.just("test-A"));
|
when(roleService.getRolesByUsername("fake-user")).thenReturn(Flux.just("test-A"));
|
||||||
when(roleService.list(Set.of("test-A"), true)).thenReturn(Flux.just(roleA));
|
when(roleService.list(Set.of("test-A"), true)).thenReturn(Flux.just(roleA));
|
||||||
|
|
||||||
|
@ -416,8 +379,6 @@ class UserEndpointTest {
|
||||||
void createWhenNameDuplicate() {
|
void createWhenNameDuplicate() {
|
||||||
when(userService.createUser(any(User.class), anySet()))
|
when(userService.createUser(any(User.class), anySet()))
|
||||||
.thenReturn(Mono.just(new User()));
|
.thenReturn(Mono.just(new User()));
|
||||||
when(userService.updateWithRawPassword(anyString(), anyString()))
|
|
||||||
.thenReturn(Mono.just(new User()));
|
|
||||||
var userRequest = new UserEndpoint.CreateUserRequest("fake-user",
|
var userRequest = new UserEndpoint.CreateUserRequest("fake-user",
|
||||||
"fake-email",
|
"fake-email",
|
||||||
"",
|
"",
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
package run.halo.app.theme.endpoint;
|
package run.halo.app.theme.endpoint;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -19,10 +16,8 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
import run.halo.app.extension.GroupVersion;
|
import run.halo.app.extension.GroupVersion;
|
||||||
import run.halo.app.extension.ListResult;
|
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.theme.finders.SinglePageFinder;
|
import run.halo.app.theme.finders.SinglePageFinder;
|
||||||
import run.halo.app.theme.finders.vo.ListedSinglePageVo;
|
|
||||||
import run.halo.app.theme.finders.vo.SinglePageVo;
|
import run.halo.app.theme.finders.vo.SinglePageVo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,30 +42,6 @@ class SinglePageQueryEndpointTest {
|
||||||
webTestClient = WebTestClient.bindToRouterFunction(endpoint.endpoint()).build();
|
webTestClient = WebTestClient.bindToRouterFunction(endpoint.endpoint()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void listSinglePages() {
|
|
||||||
ListedSinglePageVo test = ListedSinglePageVo.builder()
|
|
||||||
.metadata(metadata("test"))
|
|
||||||
.spec(new SinglePage.SinglePageSpec())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
ListResult<ListedSinglePageVo> pageResult = new ListResult<>(List.of(test));
|
|
||||||
|
|
||||||
when(singlePageFinder.list(anyInt(), anyInt(), any(), any()))
|
|
||||||
.thenReturn(Mono.just(pageResult));
|
|
||||||
|
|
||||||
webTestClient.get()
|
|
||||||
.uri("/singlepages?page=0&size=10")
|
|
||||||
.exchange()
|
|
||||||
.expectStatus().isOk()
|
|
||||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.expectBody()
|
|
||||||
.jsonPath("$.total").isEqualTo(1)
|
|
||||||
.jsonPath("$.items[0].metadata.name").isEqualTo("test");
|
|
||||||
|
|
||||||
verify(singlePageFinder).list(eq(0), eq(10), any(), any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getByName() {
|
void getByName() {
|
||||||
SinglePageVo singlePage = SinglePageVo.builder()
|
SinglePageVo singlePage = SinglePageVo.builder()
|
||||||
|
|
Loading…
Reference in New Issue