mirror of https://github.com/halo-dev/halo
feat: add public APIs for client side (#3787)
#### What type of PR is this? /kind feature /area core /milestone 2.5.x /kind api-change #### What this PR does / why we need it: 为客户端提供一套 APIs #### Which issue(s) this PR fixes: Fixes #3661 #### Does this PR introduce a user-facing change? ```release-note 为访客端提供一套完整的 API ```pull/3847/head
parent
d589ce56cc
commit
e412866749
|
@ -11,7 +11,7 @@ import reactor.core.publisher.Mono;
|
|||
import run.halo.app.core.extension.attachment.Attachment;
|
||||
|
||||
/**
|
||||
* AttachmentService
|
||||
* AttachmentService.
|
||||
*
|
||||
* @author johnniang
|
||||
* @since 2.5.0
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
import static run.halo.app.theme.endpoint.PublicApiUtils.containsElement;
|
||||
import static run.halo.app.theme.endpoint.PublicApiUtils.toAnotherListResult;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Category;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||
import run.halo.app.theme.finders.vo.CategoryVo;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
|
||||
/**
|
||||
* Endpoint for category query APIs.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CategoryQueryEndpoint implements CustomEndpoint {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final PostPublicQueryService postPublicQueryService;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = groupVersion().toString() + "/Category";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("categories", this::listCategories,
|
||||
builder -> {
|
||||
builder.operationId("queryCategories")
|
||||
.description("Lists categories.")
|
||||
.tag(tag)
|
||||
.response(responseBuilder()
|
||||
.implementation(ListResult.generateGenericClass(CategoryVo.class))
|
||||
);
|
||||
QueryParamBuildUtil.buildParametersFromType(builder, CategoryPublicQuery.class);
|
||||
}
|
||||
)
|
||||
.GET("categories/{name}", this::getByName,
|
||||
builder -> builder.operationId("queryCategoryByName")
|
||||
.description("Gets category by name.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Category name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(CategoryVo.class)
|
||||
)
|
||||
)
|
||||
.GET("categories/{name}/posts", this::listPostsByCategoryName,
|
||||
builder -> {
|
||||
builder.operationId("queryPostsByCategoryName")
|
||||
.description("Lists posts by category name.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Category name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(ListResult.generateGenericClass(ListedPostVo.class))
|
||||
);
|
||||
QueryParamBuildUtil.buildParametersFromType(builder, PostPublicQuery.class);
|
||||
}
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> listPostsByCategoryName(ServerRequest request) {
|
||||
final var name = request.pathVariable("name");
|
||||
final var query = new PostPublicQuery(request.exchange());
|
||||
Predicate<Post> categoryContainsPredicate =
|
||||
post -> containsElement(post.getSpec().getCategories(), name);
|
||||
return postPublicQueryService.list(query.getPage(),
|
||||
query.getSize(),
|
||||
categoryContainsPredicate.and(query.toPredicate()),
|
||||
query.toComparator()
|
||||
)
|
||||
.flatMap(result -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> getByName(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return client.get(Category.class, name)
|
||||
.map(CategoryVo::from)
|
||||
.flatMap(categoryVo -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(categoryVo)
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> listCategories(ServerRequest request) {
|
||||
CategoryPublicQuery query = new CategoryPublicQuery(request.exchange());
|
||||
return client.list(Category.class,
|
||||
query.toPredicate(),
|
||||
query.toComparator(),
|
||||
query.getPage(),
|
||||
query.getSize()
|
||||
)
|
||||
.map(listResult -> toAnotherListResult(listResult, CategoryVo::from))
|
||||
.flatMap(result -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
public static class CategoryPublicQuery extends SortableRequest {
|
||||
public CategoryPublicQuery(ServerWebExchange exchange) {
|
||||
super(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return PublicApiUtils.groupVersion(new Category());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Menu;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.theme.finders.MenuFinder;
|
||||
import run.halo.app.theme.finders.vo.MenuVo;
|
||||
|
||||
/**
|
||||
* Endpoint for menu query APIs.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MenuQueryEndpoint implements CustomEndpoint {
|
||||
|
||||
private final MenuFinder menuFinder;
|
||||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = groupVersion().toString() + "/Menu";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("menus/-", this::getByName,
|
||||
builder -> builder.operationId("queryPrimaryMenu")
|
||||
.description("Gets primary menu.")
|
||||
.tag(tag)
|
||||
.response(responseBuilder()
|
||||
.implementation(MenuVo.class)
|
||||
)
|
||||
)
|
||||
.GET("menus/{name}", this::getByName,
|
||||
builder -> builder.operationId("queryMenuByName")
|
||||
.description("Gets menu by name.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Menu name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(MenuVo.class)
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> getByName(ServerRequest request) {
|
||||
return determineMenuName(request)
|
||||
.flatMap(menuFinder::getByName)
|
||||
.flatMap(menuVo -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(menuVo)
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<String> determineMenuName(ServerRequest request) {
|
||||
String name = request.pathVariables().getOrDefault("name", "-");
|
||||
if (!"-".equals(name)) {
|
||||
return Mono.just(name);
|
||||
}
|
||||
// If name is "-", then get primary menu.
|
||||
return environmentFetcher.fetch(SystemSetting.Menu.GROUP, SystemSetting.Menu.class)
|
||||
.mapNotNull(SystemSetting.Menu::getPrimary)
|
||||
.switchIfEmpty(
|
||||
Mono.error(() -> new ServerWebInputException("Primary menu is not configured."))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return PublicApiUtils.groupVersion(new Menu());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Plugin;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.theme.finders.PluginFinder;
|
||||
|
||||
/**
|
||||
* Endpoint for plugin query APIs.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PluginQueryEndpoint implements CustomEndpoint {
|
||||
|
||||
private final PluginFinder pluginFinder;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = groupVersion().toString() + "/Plugin";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("plugins/{name}/available", this::availableByName,
|
||||
builder -> builder.operationId("queryPluginAvailableByName")
|
||||
.description("Gets plugin available by name.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Plugin name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(Boolean.class)
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> availableByName(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
boolean available = pluginFinder.available(name);
|
||||
return ServerResponse.ok().bodyValue(available);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return PublicApiUtils.groupVersion(new Plugin());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Query parameters for post public APIs.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class PostPublicQuery extends SortableRequest {
|
||||
|
||||
public PostPublicQuery(ServerWebExchange exchange) {
|
||||
super(exchange);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||
import run.halo.app.infra.exception.NotFoundException;
|
||||
import run.halo.app.theme.finders.PostFinder;
|
||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
import run.halo.app.theme.finders.vo.NavigationPostVo;
|
||||
import run.halo.app.theme.finders.vo.PostVo;
|
||||
|
||||
/**
|
||||
* Endpoint for post query.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PostQueryEndpoint implements CustomEndpoint {
|
||||
|
||||
private final PostFinder postFinder;
|
||||
private final PostPublicQueryService postPublicQueryService;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = groupVersion().toString() + "/Post";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("posts", this::listPosts,
|
||||
builder -> {
|
||||
builder.operationId("queryPosts")
|
||||
.description("Lists posts.")
|
||||
.tag(tag)
|
||||
.response(responseBuilder()
|
||||
.implementation(ListResult.generateGenericClass(ListedPostVo.class))
|
||||
);
|
||||
QueryParamBuildUtil.buildParametersFromType(builder, PostPublicQuery.class);
|
||||
}
|
||||
)
|
||||
.GET("posts/{name}", this::getPostByName,
|
||||
builder -> builder.operationId("queryPostByName")
|
||||
.description("Gets a post by name.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Post name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(PostVo.class)
|
||||
)
|
||||
)
|
||||
.GET("posts/{name}/navigation", this::getPostNavigationByName,
|
||||
builder -> builder.operationId("queryPostNavigationByName")
|
||||
.description("Gets a post navigation by name.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Post name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(NavigationPostVo.class)
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> getPostNavigationByName(ServerRequest request) {
|
||||
final var name = request.pathVariable("name");
|
||||
return postFinder.cursor(name)
|
||||
.doOnNext(result -> {
|
||||
if (result.getCurrent() == null) {
|
||||
throw new NotFoundException("Post not found");
|
||||
}
|
||||
})
|
||||
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> getPostByName(ServerRequest request) {
|
||||
final var name = request.pathVariable("name");
|
||||
return postFinder.getByName(name)
|
||||
.switchIfEmpty(Mono.error(() -> new NotFoundException("Post not found")))
|
||||
.flatMap(post -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(post)
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> listPosts(ServerRequest request) {
|
||||
PostPublicQuery query = new PostPublicQuery(request.exchange());
|
||||
return postPublicQueryService.list(query.getPage(), query.getSize(), query.toPredicate(),
|
||||
query.toComparator())
|
||||
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return PublicApiUtils.groupVersion(new Post());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.extension.Extension;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.GroupVersionKind;
|
||||
import run.halo.app.extension.ListResult;
|
||||
|
||||
/**
|
||||
* Utility class for public api.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@UtilityClass
|
||||
public class PublicApiUtils {
|
||||
|
||||
/**
|
||||
* Get group version from extension for public api.
|
||||
*
|
||||
* @param extension extension
|
||||
* @return <code>api.{group}/{version}</code> if group is not empty,
|
||||
* otherwise <code>api.halo.run/{version}</code>.
|
||||
*/
|
||||
public static GroupVersion groupVersion(Extension extension) {
|
||||
GroupVersionKind groupVersionKind = extension.groupVersionKind();
|
||||
String group = StringUtils.defaultIfBlank(groupVersionKind.group(), "halo.run");
|
||||
return new GroupVersion("api." + group, groupVersionKind.version());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts list result to another list result.
|
||||
*
|
||||
* @param listResult list result to be converted
|
||||
* @param mapper mapper function to convert item
|
||||
* @param <T> item type
|
||||
* @param <R> converted item type
|
||||
* @return converted list result
|
||||
*/
|
||||
public static <T, R> ListResult<R> toAnotherListResult(ListResult<T> listResult,
|
||||
Function<T, R> mapper) {
|
||||
Assert.notNull(listResult, "List result must not be null");
|
||||
Assert.notNull(mapper, "The mapper must not be null");
|
||||
List<R> mappedItems = listResult.get()
|
||||
.map(mapper)
|
||||
.toList();
|
||||
return new ListResult<>(listResult.getPage(), listResult.getSize(), listResult.getTotal(),
|
||||
mappedItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether collection contains element.
|
||||
*
|
||||
* @param <T> element type
|
||||
* @return true if collection contains element, otherwise false.
|
||||
*/
|
||||
public static <T> boolean containsElement(@Nullable Collection<T> collection,
|
||||
@Nullable T element) {
|
||||
if (collection != null && element != null) {
|
||||
return collection.contains(element);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.SinglePage;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||
import run.halo.app.theme.finders.SinglePageFinder;
|
||||
import run.halo.app.theme.finders.vo.ListedSinglePageVo;
|
||||
import run.halo.app.theme.finders.vo.SinglePageVo;
|
||||
|
||||
/**
|
||||
* Endpoint for single page query.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SinglePageQueryEndpoint implements CustomEndpoint {
|
||||
|
||||
private final SinglePageFinder singlePageFinder;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = groupVersion().toString() + "/SinglePage";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("singlepages", this::listSinglePages,
|
||||
builder -> {
|
||||
builder.operationId("querySinglePages")
|
||||
.description("Lists single pages")
|
||||
.tag(tag)
|
||||
.response(responseBuilder()
|
||||
.implementation(
|
||||
ListResult.generateGenericClass(ListedSinglePageVo.class))
|
||||
);
|
||||
QueryParamBuildUtil.buildParametersFromType(builder,
|
||||
SinglePagePublicQuery.class);
|
||||
}
|
||||
)
|
||||
.GET("singlepages/{name}", this::getByName,
|
||||
builder -> builder.operationId("querySinglePageByName")
|
||||
.description("Gets single page by name")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("SinglePage name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(SinglePageVo.class)
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> getByName(ServerRequest request) {
|
||||
var name = request.pathVariable("name");
|
||||
return singlePageFinder.getByName(name)
|
||||
.flatMap(result -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> listSinglePages(ServerRequest request) {
|
||||
var query = new SinglePagePublicQuery(request.exchange());
|
||||
return singlePageFinder.list(query.getPage(),
|
||||
query.getSize(),
|
||||
query.toPredicate(),
|
||||
query.toComparator()
|
||||
)
|
||||
.flatMap(result -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
static class SinglePagePublicQuery extends SortableRequest {
|
||||
|
||||
public SinglePagePublicQuery(ServerWebExchange exchange) {
|
||||
super(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return PublicApiUtils.groupVersion(new SinglePage());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.theme.finders.SiteStatsFinder;
|
||||
import run.halo.app.theme.finders.vo.SiteStatsVo;
|
||||
|
||||
/**
|
||||
* Endpoint for site stats query APIs.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SiteStatsQueryEndpoint implements CustomEndpoint {
|
||||
|
||||
private final SiteStatsFinder siteStatsFinder;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = groupVersion().toString() + "/Stats";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("stats/-", this::getStats,
|
||||
builder -> builder.operationId("queryStats")
|
||||
.description("Gets site stats")
|
||||
.tag(tag)
|
||||
.response(responseBuilder()
|
||||
.implementation(SiteStatsVo.class)
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> getStats(ServerRequest request) {
|
||||
return siteStatsFinder.getStats()
|
||||
.flatMap(result -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return new GroupVersion("api.halo.run", "v1alpha1");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import run.halo.app.core.extension.endpoint.SortResolver;
|
||||
import run.halo.app.extension.Extension;
|
||||
import run.halo.app.extension.router.IListRequest;
|
||||
|
||||
public class SortableRequest extends IListRequest.QueryListRequest {
|
||||
|
||||
protected final ServerWebExchange exchange;
|
||||
|
||||
public SortableRequest(ServerWebExchange exchange) {
|
||||
super(exchange.getRequest().getQueryParams());
|
||||
this.exchange = exchange;
|
||||
}
|
||||
|
||||
@ArraySchema(uniqueItems = true,
|
||||
arraySchema = @Schema(name = "sort",
|
||||
description = "Sort property and direction of the list result. Support sorting based "
|
||||
+ "on attribute name path."),
|
||||
schema = @Schema(description = "like field,asc or field,desc",
|
||||
implementation = String.class,
|
||||
example = "metadata.creationTimestamp,desc"))
|
||||
public Sort getSort() {
|
||||
return SortResolver.defaultInstance.resolve(exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build predicate from query params, default is label and field selector, you can
|
||||
* override this method to change it.
|
||||
*
|
||||
* @return predicate
|
||||
*/
|
||||
public <T extends Extension> Predicate<T> toPredicate() {
|
||||
return labelAndFieldSelectorToPredicate(getLabelSelector(), getFieldSelector());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build comparator from sort.
|
||||
*
|
||||
* @param <T> Extension type
|
||||
* @return comparator
|
||||
*/
|
||||
public <T extends Extension> Comparator<T> toComparator() {
|
||||
var sort = getSort();
|
||||
Stream<Comparator<T>> fallbackComparator =
|
||||
Stream.of(run.halo.app.extension.Comparators.compareCreationTimestamp(false),
|
||||
run.halo.app.extension.Comparators.compareName(true));
|
||||
var comparatorStream = sort.stream()
|
||||
.map(order -> {
|
||||
String property = order.getProperty();
|
||||
Sort.Direction direction = order.getDirection();
|
||||
Function<T, Object> function = extension -> {
|
||||
BeanWrapper beanWrapper = new BeanWrapperImpl(extension);
|
||||
return beanWrapper.getPropertyValue(property);
|
||||
};
|
||||
Comparator<Object> nullsComparator =
|
||||
direction.isAscending() ? Comparators.nullsLow() : Comparators.nullsHigh();
|
||||
Comparator<T> comparator = Comparator.comparing(function, nullsComparator);
|
||||
if (direction.isDescending()) {
|
||||
comparator = comparator.reversed();
|
||||
}
|
||||
return comparator;
|
||||
});
|
||||
return Stream.concat(comparatorStream, fallbackComparator)
|
||||
.reduce(Comparator::thenComparing)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
import static run.halo.app.theme.endpoint.PublicApiUtils.containsElement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.core.extension.content.Tag;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||
import run.halo.app.theme.finders.TagFinder;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
import run.halo.app.theme.finders.vo.TagVo;
|
||||
|
||||
/**
|
||||
* Endpoint for tag query APIs.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class TagQueryEndpoint implements CustomEndpoint {
|
||||
|
||||
private final TagFinder tagFinder;
|
||||
private final PostPublicQueryService postPublicQueryService;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = groupVersion().toString() + "/Tag";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("tags", this::listTags,
|
||||
builder -> {
|
||||
builder.operationId("queryTags")
|
||||
.description("Lists tags")
|
||||
.tag(tag)
|
||||
.response(responseBuilder()
|
||||
.implementation(
|
||||
ListResult.generateGenericClass(TagVo.class))
|
||||
);
|
||||
QueryParamBuildUtil.buildParametersFromType(builder, TagPublicQuery.class);
|
||||
}
|
||||
)
|
||||
.GET("tags/{name}", this::getTagByName,
|
||||
builder -> builder.operationId("queryTagByName")
|
||||
.description("Gets tag by name")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Tag name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(TagVo.class)
|
||||
)
|
||||
)
|
||||
.GET("tags/{name}/posts", this::listPostsByTagName,
|
||||
builder -> {
|
||||
builder.operationId("queryPostsByTagName")
|
||||
.description("Lists posts by tag name")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Tag name")
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(ListedPostVo.class)
|
||||
);
|
||||
QueryParamBuildUtil.buildParametersFromType(builder, PostPublicQuery.class);
|
||||
}
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> getTagByName(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return tagFinder.getByName(name)
|
||||
.flatMap(tag -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(tag)
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> listPostsByTagName(ServerRequest request) {
|
||||
final var name = request.pathVariable("name");
|
||||
final var query = new PostPublicQuery(request.exchange());
|
||||
final Predicate<Post> containsTagPredicate =
|
||||
post -> containsElement(post.getSpec().getTags(), name);
|
||||
return postPublicQueryService.list(query.getPage(),
|
||||
query.getSize(),
|
||||
containsTagPredicate.and(query.toPredicate()),
|
||||
query.toComparator()
|
||||
)
|
||||
.flatMap(result -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> listTags(ServerRequest request) {
|
||||
var query = new TagPublicQuery(request.exchange());
|
||||
return tagFinder.list(query.getPage(),
|
||||
query.getSize(),
|
||||
query.toPredicate(),
|
||||
query.toComparator()
|
||||
)
|
||||
.flatMap(result -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(result)
|
||||
);
|
||||
}
|
||||
|
||||
static class TagPublicQuery extends SortableRequest {
|
||||
public TagPublicQuery(ServerWebExchange exchange) {
|
||||
super(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return PublicApiUtils.groupVersion(new Tag());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package run.halo.app.theme.finders;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import org.springframework.lang.NonNull;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
|
||||
public interface PostPublicQueryService {
|
||||
Predicate<Post> FIXED_PREDICATE = post -> post.isPublished()
|
||||
&& Objects.equals(false, post.getSpec().getDeleted())
|
||||
&& Post.VisibleEnum.PUBLIC.equals(post.getSpec().getVisible());
|
||||
|
||||
|
||||
/**
|
||||
* Lists posts page by predicate and comparator.
|
||||
*
|
||||
* @param page page number
|
||||
* @param size page size
|
||||
* @param postPredicate post predicate
|
||||
* @param comparator post comparator
|
||||
* @return list result
|
||||
*/
|
||||
Mono<ListResult<ListedPostVo>> list(Integer page, Integer size,
|
||||
Predicate<Post> postPredicate,
|
||||
Comparator<Post> comparator);
|
||||
|
||||
/**
|
||||
* Converts post to listed post vo.
|
||||
*
|
||||
* @param post post must not be null
|
||||
* @return listed post vo
|
||||
*/
|
||||
Mono<ListedPostVo> convertToListedPostVo(@NonNull Post post);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.theme.finders;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Predicate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.SinglePage;
|
||||
|
@ -21,4 +23,7 @@ public interface SinglePageFinder {
|
|||
Mono<ContentVo> content(String pageName);
|
||||
|
||||
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,6 +1,8 @@
|
|||
package run.halo.app.theme.finders;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
@ -22,5 +24,8 @@ public interface TagFinder {
|
|||
|
||||
Mono<ListResult<TagVo>> list(@Nullable Integer page, @Nullable Integer size);
|
||||
|
||||
Mono<ListResult<TagVo>> list(@Nullable Integer page, @Nullable Integer size,
|
||||
@Nullable Predicate<Tag> predicate, @Nullable Comparator<Tag> comparator);
|
||||
|
||||
Flux<TagVo> listAll();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import run.halo.app.core.extension.MenuItem;
|
|||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.infra.exception.NotFoundException;
|
||||
import run.halo.app.theme.finders.Finder;
|
||||
import run.halo.app.theme.finders.MenuFinder;
|
||||
import run.halo.app.theme.finders.vo.MenuItemVo;
|
||||
|
@ -41,7 +42,9 @@ public class MenuFinderImpl implements MenuFinder {
|
|||
public Mono<MenuVo> getByName(String name) {
|
||||
return listAsTree()
|
||||
.filter(menu -> menu.getMetadata().getName().equals(name))
|
||||
.next();
|
||||
.next()
|
||||
.switchIfEmpty(Mono.error(
|
||||
() -> new NotFoundException("Menu with name " + name + " not found")));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -59,7 +62,10 @@ public class MenuFinderImpl implements MenuFinder {
|
|||
.orElse(menuVos.get(0))
|
||||
)
|
||||
.defaultIfEmpty(menuVos.get(0));
|
||||
});
|
||||
})
|
||||
.switchIfEmpty(
|
||||
Mono.error(() -> new NotFoundException("No primary menu found"))
|
||||
);
|
||||
}
|
||||
|
||||
Flux<MenuVo> listAll() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.theme.finders.impl;
|
||||
|
||||
import static run.halo.app.theme.finders.PostPublicQueryService.FIXED_PREDICATE;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
@ -9,14 +11,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
@ -25,20 +23,15 @@ import run.halo.app.core.extension.content.Post;
|
|||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.utils.HaloUtils;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
import run.halo.app.theme.finders.CategoryFinder;
|
||||
import run.halo.app.theme.finders.ContributorFinder;
|
||||
import run.halo.app.theme.finders.Finder;
|
||||
import run.halo.app.theme.finders.PostFinder;
|
||||
import run.halo.app.theme.finders.TagFinder;
|
||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||
import run.halo.app.theme.finders.vo.ContentVo;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
import run.halo.app.theme.finders.vo.NavigationPostVo;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveVo;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveYearMonthVo;
|
||||
import run.halo.app.theme.finders.vo.PostVo;
|
||||
import run.halo.app.theme.finders.vo.StatsVo;
|
||||
|
||||
/**
|
||||
* A finder for {@link Post}.
|
||||
|
@ -50,25 +43,18 @@ import run.halo.app.theme.finders.vo.StatsVo;
|
|||
@AllArgsConstructor
|
||||
public class PostFinderImpl implements PostFinder {
|
||||
|
||||
public static final Predicate<Post> FIXED_PREDICATE = post -> post.isPublished()
|
||||
&& Objects.equals(false, post.getSpec().getDeleted())
|
||||
&& Post.VisibleEnum.PUBLIC.equals(post.getSpec().getVisible());
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final TagFinder tagFinder;
|
||||
|
||||
private final CategoryFinder categoryFinder;
|
||||
|
||||
private final ContributorFinder contributorFinder;
|
||||
|
||||
private final CounterService counterService;
|
||||
private final PostPublicQueryService postPublicQueryService;
|
||||
|
||||
@Override
|
||||
public Mono<PostVo> getByName(String postName) {
|
||||
return client.fetch(Post.class, postName)
|
||||
.flatMap(this::getListedPostVo)
|
||||
return client.get(Post.class, postName)
|
||||
.filter(FIXED_PREDICATE)
|
||||
.flatMap(postPublicQueryService::convertToListedPostVo)
|
||||
.map(PostVo::from)
|
||||
.flatMap(postVo -> content(postName)
|
||||
.doOnNext(postVo::setContent)
|
||||
|
@ -112,7 +98,7 @@ public class PostFinderImpl implements PostFinder {
|
|||
@Override
|
||||
public Flux<ListedPostVo> listAll() {
|
||||
return client.list(Post.class, FIXED_PREDICATE, defaultComparator())
|
||||
.concatMap(this::getListedPostVo);
|
||||
.concatMap(postPublicQueryService::convertToListedPostVo);
|
||||
}
|
||||
|
||||
static Pair<String, String> postPreviousNextPair(List<String> postNames,
|
||||
|
@ -189,25 +175,25 @@ public class PostFinderImpl implements PostFinder {
|
|||
|
||||
@Override
|
||||
public Mono<ListResult<ListedPostVo>> list(Integer page, Integer size) {
|
||||
return listPost(page, size, null, defaultComparator());
|
||||
return postPublicQueryService.list(page, size, null, defaultComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<ListedPostVo>> listByCategory(Integer page, Integer size,
|
||||
String categoryName) {
|
||||
return listPost(page, size,
|
||||
return postPublicQueryService.list(page, size,
|
||||
post -> contains(post.getSpec().getCategories(), categoryName), defaultComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<ListedPostVo>> listByTag(Integer page, Integer size, String tag) {
|
||||
return listPost(page, size,
|
||||
return postPublicQueryService.list(page, size,
|
||||
post -> contains(post.getSpec().getTags(), tag), defaultComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<ListedPostVo>> listByOwner(Integer page, Integer size, String owner) {
|
||||
return listPost(page, size,
|
||||
return postPublicQueryService.list(page, size,
|
||||
post -> post.getSpec().getOwner().equals(owner), defaultComparator());
|
||||
}
|
||||
|
||||
|
@ -224,7 +210,7 @@ public class PostFinderImpl implements PostFinder {
|
|||
@Override
|
||||
public Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size, String year,
|
||||
String month) {
|
||||
return listPost(page, size, post -> {
|
||||
return postPublicQueryService.list(page, size, post -> {
|
||||
Map<String, String> labels = post.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
return false;
|
||||
|
@ -277,84 +263,6 @@ public class PostFinderImpl implements PostFinder {
|
|||
return c.contains(key);
|
||||
}
|
||||
|
||||
private Mono<ListResult<ListedPostVo>> listPost(Integer page, Integer size,
|
||||
Predicate<Post> postPredicate,
|
||||
Comparator<Post> comparator) {
|
||||
Predicate<Post> predicate = FIXED_PREDICATE
|
||||
.and(postPredicate == null ? post -> true : postPredicate);
|
||||
return client.list(Post.class, predicate,
|
||||
comparator, pageNullSafe(page), sizeNullSafe(size))
|
||||
.flatMap(list -> Flux.fromStream(list.get())
|
||||
.concatMap(post -> getListedPostVo(post)
|
||||
.flatMap(postVo -> populateStats(postVo)
|
||||
.doOnNext(postVo::setStats).thenReturn(postVo)
|
||||
)
|
||||
)
|
||||
.collectList()
|
||||
.map(postVos -> new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
|
||||
postVos)
|
||||
)
|
||||
)
|
||||
.defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
|
||||
}
|
||||
|
||||
private <T extends ListedPostVo> Mono<StatsVo> populateStats(T postVo) {
|
||||
return counterService.getByName(MeterUtils.nameOf(Post.class, postVo.getMetadata()
|
||||
.getName()))
|
||||
.map(counter -> StatsVo.builder()
|
||||
.visit(counter.getVisit())
|
||||
.upvote(counter.getUpvote())
|
||||
.comment(counter.getApprovedComment())
|
||||
.build()
|
||||
)
|
||||
.defaultIfEmpty(StatsVo.empty());
|
||||
}
|
||||
|
||||
private Mono<ListedPostVo> getListedPostVo(@NonNull Post post) {
|
||||
ListedPostVo postVo = ListedPostVo.from(post);
|
||||
postVo.setCategories(List.of());
|
||||
postVo.setTags(List.of());
|
||||
postVo.setContributors(List.of());
|
||||
|
||||
return Mono.just(postVo)
|
||||
.flatMap(lp -> populateStats(postVo)
|
||||
.doOnNext(lp::setStats)
|
||||
.thenReturn(lp)
|
||||
)
|
||||
.flatMap(p -> {
|
||||
String owner = p.getSpec().getOwner();
|
||||
return contributorFinder.getContributor(owner)
|
||||
.doOnNext(p::setOwner)
|
||||
.thenReturn(p);
|
||||
})
|
||||
.flatMap(p -> {
|
||||
List<String> tagNames = p.getSpec().getTags();
|
||||
if (CollectionUtils.isEmpty(tagNames)) {
|
||||
return Mono.just(p);
|
||||
}
|
||||
return tagFinder.getByNames(tagNames)
|
||||
.collectList()
|
||||
.doOnNext(p::setTags)
|
||||
.thenReturn(p);
|
||||
})
|
||||
.flatMap(p -> {
|
||||
List<String> categoryNames = p.getSpec().getCategories();
|
||||
if (CollectionUtils.isEmpty(categoryNames)) {
|
||||
return Mono.just(p);
|
||||
}
|
||||
return categoryFinder.getByNames(categoryNames)
|
||||
.collectList()
|
||||
.doOnNext(p::setCategories)
|
||||
.thenReturn(p);
|
||||
})
|
||||
.flatMap(p -> contributorFinder.getContributors(p.getStatus().getContributors())
|
||||
.collectList()
|
||||
.doOnNext(p::setContributors)
|
||||
.thenReturn(p)
|
||||
)
|
||||
.defaultIfEmpty(postVo);
|
||||
}
|
||||
|
||||
static Comparator<Post> defaultComparator() {
|
||||
Function<Post, Boolean> pinned =
|
||||
post -> Objects.requireNonNullElse(post.getSpec().getPinned(), false);
|
||||
|
@ -378,12 +286,4 @@ public class PostFinderImpl implements PostFinder {
|
|||
.thenComparing(name)
|
||||
.reversed();
|
||||
}
|
||||
|
||||
int pageNullSafe(Integer page) {
|
||||
return ObjectUtils.defaultIfNull(page, 1);
|
||||
}
|
||||
|
||||
int sizeNullSafe(Integer size) {
|
||||
return ObjectUtils.defaultIfNull(size, 10);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package run.halo.app.theme.finders.impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
import run.halo.app.theme.finders.CategoryFinder;
|
||||
import run.halo.app.theme.finders.ContributorFinder;
|
||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||
import run.halo.app.theme.finders.TagFinder;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
import run.halo.app.theme.finders.vo.StatsVo;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PostPublicQueryServiceImpl implements PostPublicQueryService {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
private final TagFinder tagFinder;
|
||||
|
||||
private final CategoryFinder categoryFinder;
|
||||
|
||||
private final ContributorFinder contributorFinder;
|
||||
|
||||
private final CounterService counterService;
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<ListedPostVo>> list(Integer page, Integer size,
|
||||
Predicate<Post> postPredicate, Comparator<Post> comparator) {
|
||||
Predicate<Post> predicate = FIXED_PREDICATE
|
||||
.and(postPredicate == null ? post -> true : postPredicate);
|
||||
return client.list(Post.class, predicate,
|
||||
comparator, pageNullSafe(page), sizeNullSafe(size))
|
||||
.flatMap(list -> Flux.fromStream(list.get())
|
||||
.concatMap(post -> convertToListedPostVo(post)
|
||||
.flatMap(postVo -> populateStats(postVo)
|
||||
.doOnNext(postVo::setStats).thenReturn(postVo)
|
||||
)
|
||||
)
|
||||
.collectList()
|
||||
.map(postVos -> new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
|
||||
postVos)
|
||||
)
|
||||
)
|
||||
.defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListedPostVo> convertToListedPostVo(@NonNull Post post) {
|
||||
Assert.notNull(post, "Post must not be null");
|
||||
ListedPostVo postVo = ListedPostVo.from(post);
|
||||
postVo.setCategories(List.of());
|
||||
postVo.setTags(List.of());
|
||||
postVo.setContributors(List.of());
|
||||
|
||||
return Mono.just(postVo)
|
||||
.flatMap(lp -> populateStats(postVo)
|
||||
.doOnNext(lp::setStats)
|
||||
.thenReturn(lp)
|
||||
)
|
||||
.flatMap(p -> {
|
||||
String owner = p.getSpec().getOwner();
|
||||
return contributorFinder.getContributor(owner)
|
||||
.doOnNext(p::setOwner)
|
||||
.thenReturn(p);
|
||||
})
|
||||
.flatMap(p -> {
|
||||
List<String> tagNames = p.getSpec().getTags();
|
||||
if (CollectionUtils.isEmpty(tagNames)) {
|
||||
return Mono.just(p);
|
||||
}
|
||||
return tagFinder.getByNames(tagNames)
|
||||
.collectList()
|
||||
.doOnNext(p::setTags)
|
||||
.thenReturn(p);
|
||||
})
|
||||
.flatMap(p -> {
|
||||
List<String> categoryNames = p.getSpec().getCategories();
|
||||
if (CollectionUtils.isEmpty(categoryNames)) {
|
||||
return Mono.just(p);
|
||||
}
|
||||
return categoryFinder.getByNames(categoryNames)
|
||||
.collectList()
|
||||
.doOnNext(p::setCategories)
|
||||
.thenReturn(p);
|
||||
})
|
||||
.flatMap(p -> contributorFinder.getContributors(p.getStatus().getContributors())
|
||||
.collectList()
|
||||
.doOnNext(p::setContributors)
|
||||
.thenReturn(p)
|
||||
)
|
||||
.defaultIfEmpty(postVo);
|
||||
}
|
||||
|
||||
private <T extends ListedPostVo> Mono<StatsVo> populateStats(T postVo) {
|
||||
return counterService.getByName(MeterUtils.nameOf(Post.class, postVo.getMetadata()
|
||||
.getName())
|
||||
)
|
||||
.map(counter -> StatsVo.builder()
|
||||
.visit(counter.getVisit())
|
||||
.upvote(counter.getUpvote())
|
||||
.comment(counter.getApprovedComment())
|
||||
.build()
|
||||
)
|
||||
.defaultIfEmpty(StatsVo.empty());
|
||||
}
|
||||
|
||||
int pageNullSafe(Integer page) {
|
||||
return ObjectUtils.defaultIfNull(page, 1);
|
||||
}
|
||||
|
||||
int sizeNullSafe(Integer size) {
|
||||
return ObjectUtils.defaultIfNull(size, 10);
|
||||
}
|
||||
}
|
|
@ -4,10 +4,12 @@ import java.time.Instant;
|
|||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
@ -50,7 +52,7 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
|||
|
||||
@Override
|
||||
public Mono<SinglePageVo> getByName(String pageName) {
|
||||
return client.fetch(SinglePage.class, pageName)
|
||||
return client.get(SinglePage.class, pageName)
|
||||
.filter(FIXED_PREDICATE)
|
||||
.map(page -> {
|
||||
SinglePageVo pageVo = SinglePageVo.from(page);
|
||||
|
@ -82,8 +84,19 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
|||
|
||||
@Override
|
||||
public Mono<ListResult<ListedSinglePageVo>> list(Integer page, Integer size) {
|
||||
return client.list(SinglePage.class, FIXED_PREDICATE,
|
||||
defaultComparator(), pageNullSafe(page), sizeNullSafe(size))
|
||||
return list(page, size, null, null);
|
||||
}
|
||||
|
||||
@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())
|
||||
.map(singlePage -> {
|
||||
ListedSinglePageVo pageVo = ListedSinglePageVo.from(singlePage);
|
||||
|
|
|
@ -2,8 +2,11 @@ package run.halo.app.theme.finders.impl;
|
|||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Tag;
|
||||
|
@ -45,15 +48,24 @@ public class TagFinderImpl implements TagFinder {
|
|||
|
||||
@Override
|
||||
public Mono<ListResult<TagVo>> list(Integer page, Integer size) {
|
||||
return client.list(Tag.class, null,
|
||||
DEFAULT_COMPARATOR.reversed(), pageNullSafe(page), sizeNullSafe(size))
|
||||
return list(page, size, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<TagVo>> list(@Nullable Integer page, @Nullable Integer size,
|
||||
@Nullable Predicate<Tag> predicate, @Nullable Comparator<Tag> comparator) {
|
||||
Comparator<Tag> comparatorToUse = Optional.ofNullable(comparator)
|
||||
.orElse(DEFAULT_COMPARATOR.reversed());
|
||||
return client.list(Tag.class, predicate,
|
||||
comparatorToUse, pageNullSafe(page), sizeNullSafe(size))
|
||||
.map(list -> {
|
||||
List<TagVo> tagVos = list.get()
|
||||
.map(TagVo::from)
|
||||
.collect(Collectors.toList());
|
||||
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), tagVos);
|
||||
})
|
||||
.defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
|
||||
.defaultIfEmpty(
|
||||
new ListResult<>(pageNullSafe(page), sizeNullSafe(size), 0L, List.of()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package run.halo.app.theme.finders.vo;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
|
@ -13,10 +16,12 @@ import lombok.Value;
|
|||
@Builder
|
||||
public class NavigationPostVo {
|
||||
|
||||
@Schema(requiredMode = NOT_REQUIRED)
|
||||
PostVo previous;
|
||||
|
||||
PostVo current;
|
||||
|
||||
@Schema(requiredMode = NOT_REQUIRED)
|
||||
PostVo next;
|
||||
|
||||
public boolean hasNext() {
|
||||
|
|
|
@ -2,6 +2,7 @@ package run.halo.app.theme.router.factories;
|
|||
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
import static run.halo.app.theme.finders.PostPublicQueryService.FIXED_PREDICATE;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
@ -37,7 +38,6 @@ import run.halo.app.infra.exception.NotFoundException;
|
|||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.theme.DefaultTemplateEnum;
|
||||
import run.halo.app.theme.finders.PostFinder;
|
||||
import run.halo.app.theme.finders.impl.PostFinderImpl;
|
||||
import run.halo.app.theme.finders.vo.PostVo;
|
||||
import run.halo.app.theme.router.ViewNameResolver;
|
||||
|
||||
|
@ -143,13 +143,13 @@ public class PostRouteFactory implements RouteFactory {
|
|||
|
||||
private Flux<Post> fetchPostsByName(String name) {
|
||||
return client.fetch(Post.class, name)
|
||||
.filter(PostFinderImpl.FIXED_PREDICATE)
|
||||
.filter(FIXED_PREDICATE)
|
||||
.flux();
|
||||
}
|
||||
|
||||
private Flux<Post> fetchPostsBySlug(String slug) {
|
||||
return client.list(Post.class,
|
||||
post -> PostFinderImpl.FIXED_PREDICATE.test(post)
|
||||
post -> FIXED_PREDICATE.test(post)
|
||||
&& matchIfPresent(slug, post.getSpec().getSlug()),
|
||||
null);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ metadata:
|
|||
halo.run/hidden: "true"
|
||||
annotations:
|
||||
rbac.authorization.halo.run/dependencies: |
|
||||
[ "role-template-own-permissions"]
|
||||
[ "role-template-own-permissions", "role-template-public-apis" ]
|
||||
rules:
|
||||
- apiGroups: [ "api.halo.run" ]
|
||||
resources: [ "comments", "comments/reply" ]
|
||||
|
@ -21,5 +21,23 @@ rules:
|
|||
verbs: [ "get" ]
|
||||
- nonResourceURLs: [ "/apis/api.halo.run/v1alpha1/trackers/*" ]
|
||||
verbs: [ "create" ]
|
||||
- nonResourceURLs: [ "/actuator/globalinfo", "/actuator/health", "/actuator/health/*", "/login/public-key"]
|
||||
- nonResourceURLs: [ "/actuator/globalinfo", "/actuator/health", "/actuator/health/*", "/login/public-key" ]
|
||||
verbs: [ "get" ]
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: "Role"
|
||||
metadata:
|
||||
name: role-template-public-apis
|
||||
labels:
|
||||
halo.run/role-template: "true"
|
||||
halo.run/hidden: "true"
|
||||
rules:
|
||||
- apiGroups: [ "api.halo.run" ]
|
||||
resources: [ "*" ]
|
||||
verbs: [ "get", "list" ]
|
||||
- apiGroups: [ "api.content.halo.run" ]
|
||||
resources: [ "*" ]
|
||||
verbs: [ "get", "list" ]
|
||||
- apiGroups: [ "api.plugin.halo.run" ]
|
||||
resources: [ "*" ]
|
||||
verbs: [ "get", "list" ]
|
|
@ -0,0 +1,105 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
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.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Category;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
|
||||
/**
|
||||
* Tests for {@link CategoryQueryEndpoint}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CategoryQueryEndpointTest {
|
||||
|
||||
@Mock
|
||||
private ReactiveExtensionClient client;
|
||||
|
||||
@Mock
|
||||
private PostPublicQueryService postPublicQueryService;
|
||||
private CategoryQueryEndpoint endpoint;
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
endpoint = new CategoryQueryEndpoint(client, postPublicQueryService);
|
||||
RouterFunction<ServerResponse> routerFunction = endpoint.endpoint();
|
||||
webTestClient = WebTestClient.bindToRouterFunction(routerFunction).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listCategories() {
|
||||
ListResult<Category> listResult = new ListResult<>(List.of());
|
||||
when(client.list(eq(Category.class), any(), any(), anyInt(), anyInt()))
|
||||
.thenReturn(Mono.just(listResult));
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/categories?page=1&size=10")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.total").isEqualTo(listResult.getTotal())
|
||||
.jsonPath("$.items").isArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByName() {
|
||||
Category category = new Category();
|
||||
category.setMetadata(new Metadata());
|
||||
category.getMetadata().setName("test");
|
||||
when(client.get(eq(Category.class), eq("test"))).thenReturn(Mono.just(category));
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/categories/test")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.metadata.name").isEqualTo(category.getMetadata().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void listPostsByCategoryName() {
|
||||
ListResult<ListedPostVo> listResult = new ListResult<>(List.of());
|
||||
when(postPublicQueryService.list(anyInt(), anyInt(), any(), any()))
|
||||
.thenReturn(Mono.just(listResult));
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/categories/test/posts?page=1&size=10")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.total").isEqualTo(listResult.getTotal())
|
||||
.jsonPath("$.items").isArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupVersion() {
|
||||
GroupVersion groupVersion = endpoint.groupVersion();
|
||||
assertThat(groupVersion.toString()).isEqualTo("api.content.halo.run/v1alpha1");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.NonNull;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Menu;
|
||||
import run.halo.app.core.extension.MenuItem;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.theme.finders.MenuFinder;
|
||||
import run.halo.app.theme.finders.vo.MenuItemVo;
|
||||
import run.halo.app.theme.finders.vo.MenuVo;
|
||||
|
||||
/**
|
||||
* Tests for {@link MenuQueryEndpoint}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MenuQueryEndpointTest {
|
||||
|
||||
@Mock
|
||||
private MenuFinder menuFinder;
|
||||
|
||||
@Mock
|
||||
private SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||
|
||||
@InjectMocks
|
||||
private MenuQueryEndpoint endpoint;
|
||||
|
||||
private WebTestClient webClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint()).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPrimaryMenu() {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName("fake-primary");
|
||||
MenuVo menuVo = MenuVo.builder()
|
||||
.metadata(metadata)
|
||||
.spec(new Menu.Spec())
|
||||
.menuItems(List.of(MenuItemVo.from(createMenuItem("item1"))))
|
||||
.build();
|
||||
when(menuFinder.getByName(eq("fake-primary")))
|
||||
.thenReturn(Mono.just(menuVo));
|
||||
|
||||
SystemSetting.Menu menuSetting = new SystemSetting.Menu();
|
||||
menuSetting.setPrimary("fake-primary");
|
||||
when(environmentFetcher.fetch(eq(SystemSetting.Menu.GROUP), eq(SystemSetting.Menu.class)))
|
||||
.thenReturn(Mono.just(menuSetting));
|
||||
|
||||
webClient.get().uri("/menus/-")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.metadata.name").isEqualTo("fake-primary")
|
||||
.jsonPath("$.menuItems[0].metadata.name").isEqualTo("item1");
|
||||
|
||||
verify(menuFinder).getByName(eq("fake-primary"));
|
||||
verify(environmentFetcher).fetch(eq(SystemSetting.Menu.GROUP),
|
||||
eq(SystemSetting.Menu.class));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static MenuItem createMenuItem(String name) {
|
||||
MenuItem menuItem = new MenuItem();
|
||||
menuItem.setMetadata(new Metadata());
|
||||
menuItem.getMetadata().setName(name);
|
||||
menuItem.setSpec(new MenuItem.MenuItemSpec());
|
||||
menuItem.getSpec().setDisplayName(name);
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMenuByName() {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName("test-menu");
|
||||
MenuVo menuVo = MenuVo.builder()
|
||||
.metadata(metadata)
|
||||
.spec(new Menu.Spec())
|
||||
.menuItems(List.of(MenuItemVo.from(createMenuItem("item2"))))
|
||||
.build();
|
||||
when(menuFinder.getByName(eq("test-menu")))
|
||||
.thenReturn(Mono.just(menuVo));
|
||||
|
||||
webClient.get().uri("/menus/test-menu")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.metadata.name").isEqualTo("test-menu")
|
||||
.jsonPath("$.menuItems[0].metadata.name").isEqualTo("item2");
|
||||
|
||||
verify(menuFinder).getByName(eq("test-menu"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupVersion() {
|
||||
GroupVersion groupVersion = endpoint.groupVersion();
|
||||
assertThat(groupVersion.toString()).isEqualTo("api.halo.run/v1alpha1");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.theme.finders.PluginFinder;
|
||||
|
||||
/**
|
||||
* Tests for {@link PluginQueryEndpoint}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PluginQueryEndpointTest {
|
||||
|
||||
@Mock
|
||||
private PluginFinder pluginFinder;
|
||||
|
||||
@InjectMocks
|
||||
private PluginQueryEndpoint endpoint;
|
||||
|
||||
private WebTestClient webClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint()).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void available() {
|
||||
when(pluginFinder.available("fake-plugin")).thenReturn(true);
|
||||
webClient.get().uri("/plugins/fake-plugin/available")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$").isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupVersion() {
|
||||
GroupVersion groupVersion = endpoint.groupVersion();
|
||||
assertThat(groupVersion.toString()).isEqualTo("api.plugin.halo.run/v1alpha1");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
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.anyString;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.theme.finders.PostFinder;
|
||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
import run.halo.app.theme.finders.vo.NavigationPostVo;
|
||||
import run.halo.app.theme.finders.vo.PostVo;
|
||||
|
||||
/**
|
||||
* Tests for {@link PostQueryEndpoint}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PostQueryEndpointTest {
|
||||
|
||||
private WebTestClient webClient;
|
||||
|
||||
@Mock
|
||||
private PostFinder postFinder;
|
||||
|
||||
@Mock
|
||||
private PostPublicQueryService postPublicQueryService;
|
||||
|
||||
@InjectMocks
|
||||
private PostQueryEndpoint endpoint;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listPosts() {
|
||||
ListResult<ListedPostVo> result = new ListResult<>(List.of());
|
||||
when(postPublicQueryService.list(anyInt(), anyInt(), any(), any()))
|
||||
.thenReturn(Mono.just(result));
|
||||
|
||||
webClient.get().uri("/posts")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.items").isArray();
|
||||
|
||||
verify(postPublicQueryService).list(anyInt(), anyInt(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPostByName() {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName("test");
|
||||
PostVo post = PostVo.builder()
|
||||
.metadata(metadata)
|
||||
.build();
|
||||
when(postFinder.getByName(anyString())).thenReturn(Mono.just(post));
|
||||
|
||||
webClient.get().uri("/posts/{name}", "test")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.metadata.name").isEqualTo("test");
|
||||
|
||||
verify(postFinder).getByName(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPostNavigationByName() {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName("test");
|
||||
NavigationPostVo navigation = NavigationPostVo.builder()
|
||||
.current(PostVo.builder().metadata(metadata).build())
|
||||
.build();
|
||||
when(postFinder.cursor(anyString()))
|
||||
.thenReturn(Mono.just(navigation));
|
||||
|
||||
webClient.get().uri("/posts/{name}/navigation", "test")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.current.metadata.name").isEqualTo("test");
|
||||
|
||||
verify(postFinder).cursor(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupVersion() {
|
||||
GroupVersion groupVersion = endpoint.groupVersion();
|
||||
assertThat(groupVersion.toString()).isEqualTo("api.content.halo.run/v1alpha1");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.GVK;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
|
||||
/**
|
||||
* Tests for {@link PublicApiUtils}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class PublicApiUtilsTest {
|
||||
|
||||
@Test
|
||||
void groupVersion() {
|
||||
GroupVersion groupVersion = PublicApiUtils.groupVersion(new FakExtension());
|
||||
assertThat(groupVersion.toString()).isEqualTo("api.halo.run/v1alpha1");
|
||||
|
||||
groupVersion = PublicApiUtils.groupVersion(new FakeGroupExtension());
|
||||
assertThat(groupVersion.toString()).isEqualTo("api.fake.halo.run/v1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsElement() {
|
||||
assertThat(PublicApiUtils.containsElement(null, null)).isFalse();
|
||||
assertThat(PublicApiUtils.containsElement(null, "test")).isFalse();
|
||||
assertThat(PublicApiUtils.containsElement(List.of("test"), null)).isFalse();
|
||||
assertThat(PublicApiUtils.containsElement(List.of("test"), "test")).isTrue();
|
||||
assertThat(PublicApiUtils.containsElement(List.of("test"), "test1")).isFalse();
|
||||
}
|
||||
|
||||
@GVK(group = "fake.halo.run", version = "v1", kind = "FakeGroupExtension", plural =
|
||||
"fakegroupextensions", singular = "fakegroupextension")
|
||||
static class FakeGroupExtension extends AbstractExtension {
|
||||
|
||||
}
|
||||
|
||||
@GVK(group = "", version = "v1alpha1", kind = "FakeExtension", plural =
|
||||
"fakeextensions", singular = "fakeextension")
|
||||
static class FakExtension extends AbstractExtension {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
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.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.SinglePage;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.theme.finders.SinglePageFinder;
|
||||
import run.halo.app.theme.finders.vo.ListedSinglePageVo;
|
||||
import run.halo.app.theme.finders.vo.SinglePageVo;
|
||||
|
||||
/**
|
||||
* Tests for {@link SinglePageQueryEndpoint}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SinglePageQueryEndpointTest {
|
||||
|
||||
@Mock
|
||||
private SinglePageFinder singlePageFinder;
|
||||
|
||||
@InjectMocks
|
||||
private SinglePageQueryEndpoint endpoint;
|
||||
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
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
|
||||
void getByName() {
|
||||
SinglePageVo singlePage = SinglePageVo.builder()
|
||||
.metadata(metadata("fake-page"))
|
||||
.spec(new SinglePage.SinglePageSpec())
|
||||
.build();
|
||||
|
||||
when(singlePageFinder.getByName(eq("fake-page")))
|
||||
.thenReturn(Mono.just(singlePage));
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/singlepages/fake-page")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.jsonPath("$.metadata.name").isEqualTo("fake-page");
|
||||
|
||||
verify(singlePageFinder).getByName("fake-page");
|
||||
}
|
||||
|
||||
Metadata metadata(String name) {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName(name);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupVersion() {
|
||||
GroupVersion groupVersion = endpoint.groupVersion();
|
||||
assertThat(groupVersion.toString()).isEqualTo("api.content.halo.run/v1alpha1");
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import static org.mockito.ArgumentMatchers.any;
|
|||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static run.halo.app.theme.finders.PostPublicQueryService.FIXED_PREDICATE;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
|
@ -18,7 +19,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.PostService;
|
||||
|
@ -29,8 +29,10 @@ import run.halo.app.extension.ReactiveExtensionClient;
|
|||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.theme.finders.CategoryFinder;
|
||||
import run.halo.app.theme.finders.ContributorFinder;
|
||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||
import run.halo.app.theme.finders.TagFinder;
|
||||
import run.halo.app.theme.finders.vo.ContentVo;
|
||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveVo;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveYearMonthVo;
|
||||
|
||||
|
@ -61,6 +63,9 @@ class PostFinderImplTest {
|
|||
@Mock
|
||||
private ContributorFinder contributorFinder;
|
||||
|
||||
@Mock
|
||||
private PostPublicQueryService publicQueryService;
|
||||
|
||||
@InjectMocks
|
||||
private PostFinderImpl postFinder;
|
||||
|
||||
|
@ -92,7 +97,7 @@ class PostFinderImplTest {
|
|||
|
||||
@Test
|
||||
void predicate() {
|
||||
List<String> strings = posts().stream().filter(PostFinderImpl.FIXED_PREDICATE)
|
||||
List<String> strings = posts().stream().filter(FIXED_PREDICATE)
|
||||
.map(post -> post.getMetadata().getName())
|
||||
.toList();
|
||||
assertThat(strings).isEqualTo(List.of("post-1", "post-2", "post-6"));
|
||||
|
@ -100,12 +105,12 @@ class PostFinderImplTest {
|
|||
|
||||
@Test
|
||||
void archives() {
|
||||
when(counterService.getByName(any())).thenReturn(Mono.empty());
|
||||
ListResult<Post> listResult = new ListResult<>(1, 10, 3, postsForArchives());
|
||||
when(client.list(eq(Post.class), any(), any(), anyInt(), anyInt()))
|
||||
List<ListedPostVo> listedPostVos = postsForArchives().stream()
|
||||
.map(ListedPostVo::from)
|
||||
.toList();
|
||||
ListResult<ListedPostVo> listResult = new ListResult<>(1, 10, 3, listedPostVos);
|
||||
when(publicQueryService.list(anyInt(), anyInt(), any(), any()))
|
||||
.thenReturn(Mono.just(listResult));
|
||||
when(contributorFinder.getContributor(any())).thenReturn(Mono.empty());
|
||||
when(contributorFinder.getContributors(any())).thenReturn(Flux.empty());
|
||||
|
||||
ListResult<PostArchiveVo> archives = postFinder.archives(1, 10).block();
|
||||
assertThat(archives).isNotNull();
|
||||
|
|
|
@ -3,7 +3,6 @@ package run.halo.app.theme.finders.impl;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
@ -62,7 +61,7 @@ class SinglePageFinderImplTest {
|
|||
singlePage.getSpec().setDeleted(false);
|
||||
singlePage.getSpec().setVisible(Post.VisibleEnum.PUBLIC);
|
||||
singlePage.setStatus(new SinglePage.SinglePageStatus());
|
||||
when(client.fetch(eq(SinglePage.class), eq(fakePageName)))
|
||||
when(client.get(eq(SinglePage.class), eq(fakePageName)))
|
||||
.thenReturn(Mono.just(singlePage));
|
||||
|
||||
when(counterService.getByName(anyString())).thenReturn(Mono.empty());
|
||||
|
@ -77,7 +76,7 @@ class SinglePageFinderImplTest {
|
|||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(client, times(1)).fetch(SinglePage.class, fakePageName);
|
||||
verify(client).get(SinglePage.class, fakePageName);
|
||||
verify(counterService).getByName(anyString());
|
||||
verify(singlePageService).getReleaseContent(anyString());
|
||||
verify(contributorFinder).getContributor(anyString());
|
||||
|
|
Loading…
Reference in New Issue