feat: theme finders supports the reactive API (#2695)

#### What type of PR is this?
/kind feature
/area core
/milestone 2.0

#### What this PR does / why we need it:
将所有 Finder 的返回值都修改为 Mono 或 Flux

#### Which issue(s) this PR fixes:

Fixes #2671

#### Special notes for your reviewer:
how to test it?
切换几个主题都点一下,没有报错即为正常

/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

```release-note
主题端 Finder 支持 Reactive API
```
pull/2712/head
guqing 2022-11-17 10:48:22 +08:00 committed by GitHub
parent cc984b2bb3
commit 9f53cd1123
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 750 additions and 513 deletions

View File

@ -0,0 +1,121 @@
package run.halo.app.theme;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.ArrayList;
import java.util.List;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.integration.json.JsonPropertyAccessor;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.infra.utils.JsonUtils;
/**
* A SpEL PropertyAccessor that knows how to read properties from {@link Mono} or {@link Flux}
* object. It first converts the target to the actual value and then calls other
* {@link PropertyAccessor}s to parse the result, If it still cannot be resolved,
* {@link JsonPropertyAccessor} will be used to resolve finally.
*
* @author guqing
* @since 2.0.0
*/
public class ReactivePropertyAccessor implements PropertyAccessor {
private static final Class<?>[] SUPPORTED_CLASSES = {
Mono.class,
Flux.class
};
private final JsonPropertyAccessor jsonPropertyAccessor = new JsonPropertyAccessor();
@Override
public Class<?>[] getSpecificTargetClasses() {
return SUPPORTED_CLASSES;
}
@Override
public boolean canRead(@NonNull EvaluationContext context, Object target, @NonNull String name)
throws AccessException {
if (target == null) {
return false;
}
return Mono.class.isAssignableFrom(target.getClass())
|| Flux.class.isAssignableFrom(target.getClass());
}
@Override
@NonNull
public TypedValue read(@NonNull EvaluationContext context, Object target, @NonNull String name)
throws AccessException {
if (target == null) {
return TypedValue.NULL;
}
Class<?> clazz = target.getClass();
Object value = null;
if (Mono.class.isAssignableFrom(clazz)) {
value = ((Mono<?>) target).block();
} else if (Flux.class.isAssignableFrom(clazz)) {
value = ((Flux<?>) target).toIterable();
}
if (value == null) {
return TypedValue.NULL;
}
List<PropertyAccessor> propertyAccessorsToTry =
getPropertyAccessorsToTry(value, context.getPropertyAccessors());
for (PropertyAccessor propertyAccessor : propertyAccessorsToTry) {
try {
return propertyAccessor.read(context, target, name);
} catch (AccessException e) {
// ignore
}
}
JsonNode jsonNode = JsonUtils.DEFAULT_JSON_MAPPER.convertValue(value, JsonNode.class);
return jsonPropertyAccessor.read(context, jsonNode, name);
}
private List<PropertyAccessor> getPropertyAccessorsToTry(
@Nullable Object contextObject, List<PropertyAccessor> propertyAccessors) {
Class<?> targetType = (contextObject != null ? contextObject.getClass() : null);
List<PropertyAccessor> specificAccessors = new ArrayList<>();
List<PropertyAccessor> generalAccessors = new ArrayList<>();
for (PropertyAccessor resolver : propertyAccessors) {
Class<?>[] targets = resolver.getSpecificTargetClasses();
if (targets == null) {
// generic resolver that says it can be used for any type
generalAccessors.add(resolver);
} else if (targetType != null) {
for (Class<?> clazz : targets) {
if (clazz == targetType) {
specificAccessors.add(resolver);
break;
} else if (clazz.isAssignableFrom(targetType)) {
generalAccessors.add(resolver);
}
}
}
}
List<PropertyAccessor> resolvers = new ArrayList<>(specificAccessors);
generalAccessors.removeAll(specificAccessors);
resolvers.addAll(generalAccessors);
return resolvers;
}
@Override
public boolean canWrite(@NonNull EvaluationContext context, Object target, @NonNull String name)
throws AccessException {
return false;
}
@Override
public void write(@NonNull EvaluationContext context, Object target, @NonNull String name,
Object newValue)
throws AccessException {
throw new UnsupportedOperationException("Write is not supported");
}
}

View File

@ -0,0 +1,44 @@
package run.halo.app.theme;
import org.thymeleaf.context.IExpressionContext;
import org.thymeleaf.spring6.expression.SPELVariableExpressionEvaluator;
import org.thymeleaf.standard.expression.IStandardVariableExpression;
import org.thymeleaf.standard.expression.IStandardVariableExpressionEvaluator;
import org.thymeleaf.standard.expression.StandardExpressionExecutionContext;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Reactive SPEL variable expression evaluator.
*
* @author guqing
* @since 2.0.0
*/
public class ReactiveSpelVariableExpressionEvaluator
implements IStandardVariableExpressionEvaluator {
private final SPELVariableExpressionEvaluator delegate =
SPELVariableExpressionEvaluator.INSTANCE;
public static final ReactiveSpelVariableExpressionEvaluator INSTANCE =
new ReactiveSpelVariableExpressionEvaluator();
@Override
public Object evaluate(IExpressionContext context, IStandardVariableExpression expression,
StandardExpressionExecutionContext expContext) {
Object returnValue = delegate.evaluate(context, expression, expContext);
if (returnValue == null) {
return null;
}
Class<?> clazz = returnValue.getClass();
// Note that: 3 instanceof Foo -> syntax error
if (Mono.class.isAssignableFrom(clazz)) {
return ((Mono<?>) returnValue).block();
}
if (Flux.class.isAssignableFrom(clazz)) {
return ((Flux<?>) returnValue).toIterable();
}
return returnValue;
}
}

View File

@ -9,6 +9,8 @@ import org.springframework.util.ConcurrentLruCache;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
import org.thymeleaf.dialect.IDialect; import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine; import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring6.dialect.SpringStandardDialect;
import org.thymeleaf.standard.expression.IStandardVariableExpressionEvaluator;
import org.thymeleaf.templateresolver.FileTemplateResolver; import org.thymeleaf.templateresolver.FileTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ITemplateResolver;
import run.halo.app.infra.ExternalUrlSupplier; import run.halo.app.infra.ExternalUrlSupplier;
@ -86,6 +88,13 @@ public class TemplateEngineManager {
var mainResolver = haloTemplateResolver(); var mainResolver = haloTemplateResolver();
mainResolver.setPrefix(theme.getPath() + "/templates/"); mainResolver.setPrefix(theme.getPath() + "/templates/");
engine.addTemplateResolver(mainResolver); engine.addTemplateResolver(mainResolver);
// replace StandardDialect with SpringStandardDialect
engine.setDialect(new SpringStandardDialect() {
@Override
public IStandardVariableExpressionEvaluator getVariableExpressionEvaluator() {
return ReactiveSpelVariableExpressionEvaluator.INSTANCE;
}
});
engine.addDialect(new HaloProcessorDialect()); engine.addDialect(new HaloProcessorDialect());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver); templateResolvers.orderedStream().forEach(engine::addTemplateResolver);

View File

@ -4,9 +4,7 @@ import java.util.Map;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.theme.finders.ThemeFinder; import run.halo.app.theme.finders.ThemeFinder;
import run.halo.app.theme.finders.vo.ThemeVo;
/** /**
* Theme context based variables acquirer. * Theme context based variables acquirer.
@ -28,14 +26,11 @@ public class ThemeContextBasedVariablesAcquirer implements ViewContextBasedVaria
@Override @Override
public Mono<Map<String, Object>> acquire(ServerWebExchange exchange) { public Mono<Map<String, Object>> acquire(ServerWebExchange exchange) {
return themeResolver.getTheme(exchange.getRequest()) return themeResolver.getTheme(exchange.getRequest())
.publishOn(Schedulers.boundedElastic()) .flatMap(themeContext -> {
.map(themeContext -> {
String name = themeContext.getName(); String name = themeContext.getName();
ThemeVo themeVo = themeFinder.getByName(name); return themeFinder.getByName(name);
if (themeVo == null) { })
return Map.of(); .map(themeVo -> Map.<String, Object>of("theme", themeVo))
} .defaultIfEmpty(Map.of());
return Map.of("theme", themeVo);
});
} }
} }

View File

@ -9,6 +9,7 @@ import org.thymeleaf.processor.templateboundaries.ITemplateBoundariesStructureHa
import org.thymeleaf.spring6.expression.ThymeleafEvaluationContext; import org.thymeleaf.spring6.expression.ThymeleafEvaluationContext;
import org.thymeleaf.standard.StandardDialect; import org.thymeleaf.standard.StandardDialect;
import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templatemode.TemplateMode;
import run.halo.app.theme.ReactivePropertyAccessor;
/** /**
* A template boundaries processor for add {@link JsonPropertyAccessor} to * A template boundaries processor for add {@link JsonPropertyAccessor} to
@ -21,6 +22,8 @@ public class JsonNodePropertyAccessorBoundariesProcessor
extends AbstractTemplateBoundariesProcessor { extends AbstractTemplateBoundariesProcessor {
private static final int PRECEDENCE = StandardDialect.PROCESSOR_PRECEDENCE; private static final int PRECEDENCE = StandardDialect.PROCESSOR_PRECEDENCE;
private static final JsonPropertyAccessor JSON_PROPERTY_ACCESSOR = new JsonPropertyAccessor(); private static final JsonPropertyAccessor JSON_PROPERTY_ACCESSOR = new JsonPropertyAccessor();
private static final ReactivePropertyAccessor REACTIVE_PROPERTY_ACCESSOR =
new ReactivePropertyAccessor();
public JsonNodePropertyAccessorBoundariesProcessor() { public JsonNodePropertyAccessorBoundariesProcessor() {
super(TemplateMode.HTML, PRECEDENCE); super(TemplateMode.HTML, PRECEDENCE);
@ -34,6 +37,7 @@ public class JsonNodePropertyAccessorBoundariesProcessor
ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME); ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME);
if (evaluationContext != null) { if (evaluationContext != null) {
evaluationContext.addPropertyAccessor(JSON_PROPERTY_ACCESSOR); evaluationContext.addPropertyAccessor(JSON_PROPERTY_ACCESSOR);
evaluationContext.addPropertyAccessor(REACTIVE_PROPERTY_ACCESSOR);
} }
} }

View File

@ -36,7 +36,7 @@ public class PostTemplateHeadProcessor implements TemplateHeadProcessor {
return Mono.empty(); return Mono.empty();
} }
return Mono.justOrEmpty((String) context.getVariable(POST_NAME_VARIABLE)) return Mono.justOrEmpty((String) context.getVariable(POST_NAME_VARIABLE))
.map(postFinder::getByName) .flatMap(postFinder::getByName)
.doOnNext(postVo -> { .doOnNext(postVo -> {
List<Map<String, String>> htmlMetas = postVo.getSpec().getHtmlMetas(); List<Map<String, String>> htmlMetas = postVo.getSpec().getHtmlMetas();
String metaHtml = headMetaBuilder(htmlMetas); String metaHtml = headMetaBuilder(htmlMetas);

View File

@ -165,10 +165,8 @@ public class CommentFinderEndpoint implements CustomEndpoint {
Mono<ServerResponse> listComments(ServerRequest request) { Mono<ServerResponse> listComments(ServerRequest request) {
CommentQuery commentQuery = new CommentQuery(request.queryParams()); CommentQuery commentQuery = new CommentQuery(request.queryParams());
return Mono.defer(() -> Mono.just( return commentFinder.list(commentQuery.toRef(), commentQuery.getPage(),
commentFinder.list(commentQuery.toRef(), commentQuery.getPage(), commentQuery.getSize())
commentQuery.getSize())))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(list -> ServerResponse.ok().bodyValue(list)); .flatMap(list -> ServerResponse.ok().bodyValue(list));
} }
@ -183,9 +181,7 @@ public class CommentFinderEndpoint implements CustomEndpoint {
String commentName = request.pathVariable("name"); String commentName = request.pathVariable("name");
IListRequest.QueryListRequest queryParams = IListRequest.QueryListRequest queryParams =
new IListRequest.QueryListRequest(request.queryParams()); new IListRequest.QueryListRequest(request.queryParams());
return Mono.defer(() -> Mono.just( return commentFinder.listReply(commentName, queryParams.getPage(), queryParams.getSize())
commentFinder.listReply(commentName, queryParams.getPage(), queryParams.getSize())))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(list -> ServerResponse.ok().bodyValue(list)); .flatMap(list -> ServerResponse.ok().bodyValue(list));
} }

View File

@ -2,6 +2,8 @@ package run.halo.app.theme.finders;
import java.util.List; import java.util.List;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Category; import run.halo.app.core.extension.Category;
import run.halo.app.extension.ListResult; import run.halo.app.extension.ListResult;
import run.halo.app.theme.finders.vo.CategoryTreeVo; import run.halo.app.theme.finders.vo.CategoryTreeVo;
@ -15,13 +17,13 @@ import run.halo.app.theme.finders.vo.CategoryVo;
*/ */
public interface CategoryFinder { public interface CategoryFinder {
CategoryVo getByName(String name); Mono<CategoryVo> getByName(String name);
List<CategoryVo> getByNames(List<String> names); Flux<CategoryVo> getByNames(List<String> names);
ListResult<CategoryVo> list(@Nullable Integer page, @Nullable Integer size); Mono<ListResult<CategoryVo>> list(@Nullable Integer page, @Nullable Integer size);
List<CategoryVo> listAll(); Flux<CategoryVo> listAll();
List<CategoryTreeVo> listAsTree(); Flux<CategoryTreeVo> listAsTree();
} }

View File

@ -1,6 +1,7 @@
package run.halo.app.theme.finders; package run.halo.app.theme.finders;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Comment; import run.halo.app.core.extension.Comment;
import run.halo.app.extension.ListResult; import run.halo.app.extension.ListResult;
import run.halo.app.extension.Ref; import run.halo.app.extension.Ref;
@ -15,11 +16,11 @@ import run.halo.app.theme.finders.vo.ReplyVo;
*/ */
public interface CommentFinder { public interface CommentFinder {
CommentVo getByName(String name); Mono<CommentVo> getByName(String name);
ListResult<CommentVo> list(Ref ref, @Nullable Integer page, Mono<ListResult<CommentVo>> list(Ref ref, @Nullable Integer page,
@Nullable Integer size); @Nullable Integer size);
ListResult<ReplyVo> listReply(String commentName, @Nullable Integer page, Mono<ListResult<ReplyVo>> listReply(String commentName, @Nullable Integer page,
@Nullable Integer size); @Nullable Integer size);
} }

View File

@ -1,6 +1,8 @@
package run.halo.app.theme.finders; package run.halo.app.theme.finders;
import java.util.List; import java.util.List;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.User; import run.halo.app.core.extension.User;
import run.halo.app.theme.finders.vo.Contributor; import run.halo.app.theme.finders.vo.Contributor;
@ -9,7 +11,7 @@ import run.halo.app.theme.finders.vo.Contributor;
*/ */
public interface ContributorFinder { public interface ContributorFinder {
Contributor getContributor(String name); Mono<Contributor> getContributor(String name);
List<Contributor> getContributors(List<String> names); Flux<Contributor> getContributors(List<String> names);
} }

View File

@ -1,5 +1,6 @@
package run.halo.app.theme.finders; package run.halo.app.theme.finders;
import reactor.core.publisher.Mono;
import run.halo.app.theme.finders.vo.MenuVo; import run.halo.app.theme.finders.vo.MenuVo;
/** /**
@ -10,7 +11,7 @@ import run.halo.app.theme.finders.vo.MenuVo;
*/ */
public interface MenuFinder { public interface MenuFinder {
MenuVo getByName(String name); Mono<MenuVo> getByName(String name);
MenuVo getPrimary(); Mono<MenuVo> getPrimary();
} }

View File

@ -1,9 +1,11 @@
package run.halo.app.theme.finders; package run.halo.app.theme.finders;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Post; import run.halo.app.core.extension.Post;
import run.halo.app.extension.ListResult; import run.halo.app.extension.ListResult;
import run.halo.app.theme.finders.vo.ContentVo; 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.NavigationPostVo;
import run.halo.app.theme.finders.vo.PostArchiveVo; import run.halo.app.theme.finders.vo.PostArchiveVo;
import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.finders.vo.PostVo;
@ -16,22 +18,23 @@ import run.halo.app.theme.finders.vo.PostVo;
*/ */
public interface PostFinder { public interface PostFinder {
PostVo getByName(String postName); Mono<PostVo> getByName(String postName);
ContentVo content(String postName); Mono<ContentVo> content(String postName);
NavigationPostVo cursor(String current); Mono<NavigationPostVo> cursor(String current);
ListResult<PostVo> list(@Nullable Integer page, @Nullable Integer size); Mono<ListResult<ListedPostVo>> list(@Nullable Integer page, @Nullable Integer size);
ListResult<PostVo> listByCategory(@Nullable Integer page, @Nullable Integer size, Mono<ListResult<ListedPostVo>> listByCategory(@Nullable Integer page, @Nullable Integer size,
String categoryName); String categoryName);
ListResult<PostVo> listByTag(@Nullable Integer page, @Nullable Integer size, String tag); Mono<ListResult<ListedPostVo>> listByTag(@Nullable Integer page, @Nullable Integer size,
String tag);
ListResult<PostArchiveVo> archives(Integer page, Integer size); Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size);
ListResult<PostArchiveVo> archives(Integer page, Integer size, String year); Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size, String year);
ListResult<PostArchiveVo> archives(Integer page, Integer size, String year, String month); Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size, String year, String month);
} }

View File

@ -1,9 +1,11 @@
package run.halo.app.theme.finders; package run.halo.app.theme.finders;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.SinglePage; import run.halo.app.core.extension.SinglePage;
import run.halo.app.extension.ListResult; import run.halo.app.extension.ListResult;
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.SinglePageVo; import run.halo.app.theme.finders.vo.SinglePageVo;
/** /**
@ -14,9 +16,9 @@ import run.halo.app.theme.finders.vo.SinglePageVo;
*/ */
public interface SinglePageFinder { public interface SinglePageFinder {
SinglePageVo getByName(String pageName); Mono<SinglePageVo> getByName(String pageName);
ContentVo content(String pageName); Mono<ContentVo> content(String pageName);
ListResult<SinglePageVo> list(@Nullable Integer page, @Nullable Integer size); Mono<ListResult<ListedSinglePageVo>> list(@Nullable Integer page, @Nullable Integer size);
} }

View File

@ -1,5 +1,6 @@
package run.halo.app.theme.finders; package run.halo.app.theme.finders;
import reactor.core.publisher.Mono;
import run.halo.app.theme.finders.vo.SiteStatsVo; import run.halo.app.theme.finders.vo.SiteStatsVo;
/** /**
@ -10,5 +11,5 @@ import run.halo.app.theme.finders.vo.SiteStatsVo;
*/ */
public interface SiteStatsFinder { public interface SiteStatsFinder {
SiteStatsVo getStats(); Mono<SiteStatsVo> getStats();
} }

View File

@ -2,6 +2,8 @@ package run.halo.app.theme.finders;
import java.util.List; import java.util.List;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Tag; import run.halo.app.core.extension.Tag;
import run.halo.app.extension.ListResult; import run.halo.app.extension.ListResult;
import run.halo.app.theme.finders.vo.TagVo; import run.halo.app.theme.finders.vo.TagVo;
@ -14,11 +16,11 @@ import run.halo.app.theme.finders.vo.TagVo;
*/ */
public interface TagFinder { public interface TagFinder {
TagVo getByName(String name); Mono<TagVo> getByName(String name);
List<TagVo> getByNames(List<String> names); Flux<TagVo> getByNames(List<String> names);
ListResult<TagVo> list(@Nullable Integer page, @Nullable Integer size); Mono<ListResult<TagVo>> list(@Nullable Integer page, @Nullable Integer size);
List<TagVo> listAll(); Flux<TagVo> listAll();
} }

View File

@ -1,5 +1,6 @@
package run.halo.app.theme.finders; package run.halo.app.theme.finders;
import reactor.core.publisher.Mono;
import run.halo.app.theme.finders.vo.ThemeVo; import run.halo.app.theme.finders.vo.ThemeVo;
/** /**
@ -10,7 +11,7 @@ import run.halo.app.theme.finders.vo.ThemeVo;
*/ */
public interface ThemeFinder { public interface ThemeFinder {
ThemeVo activation(); Mono<ThemeVo> activation();
ThemeVo getByName(String themeName); Mono<ThemeVo> getByName(String themeName);
} }

View File

@ -10,6 +10,8 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Category; import run.halo.app.core.extension.Category;
import run.halo.app.extension.ListResult; import run.halo.app.extension.ListResult;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
@ -34,24 +36,22 @@ public class CategoryFinderImpl implements CategoryFinder {
} }
@Override @Override
public CategoryVo getByName(String name) { public Mono<CategoryVo> getByName(String name) {
return client.fetch(Category.class, name) return client.fetch(Category.class, name)
.map(CategoryVo::from) .map(CategoryVo::from);
.block();
} }
@Override @Override
public List<CategoryVo> getByNames(List<String> names) { public Flux<CategoryVo> getByNames(List<String> names) {
if (names == null) { if (names == null) {
return List.of(); return Flux.empty();
} }
return names.stream().map(this::getByName) return Flux.fromIterable(names)
.filter(Objects::nonNull) .flatMap(this::getByName);
.toList();
} }
@Override @Override
public ListResult<CategoryVo> list(Integer page, Integer size) { public Mono<ListResult<CategoryVo>> list(Integer page, Integer size) {
return client.list(Category.class, null, return client.list(Category.class, null,
defaultComparator(), pageNullSafe(page), sizeNullSafe(size)) defaultComparator(), pageNullSafe(page), sizeNullSafe(size))
.map(list -> { .map(list -> {
@ -61,20 +61,20 @@ public class CategoryFinderImpl implements CategoryFinder {
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
categoryVos); categoryVos);
}) })
.block(); .defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
} }
@Override @Override
public List<CategoryVo> listAll() { public Flux<CategoryVo> listAll() {
return client.list(Category.class, null, defaultComparator()) return client.list(Category.class, null, defaultComparator())
.map(CategoryVo::from) .map(CategoryVo::from);
.collectList()
.block();
} }
@Override @Override
public List<CategoryTreeVo> listAsTree() { public Flux<CategoryTreeVo> listAsTree() {
List<CategoryVo> categoryVos = listAll(); return listAll()
.collectList()
.flatMapIterable(categoryVos -> {
Map<String, CategoryTreeVo> nameIdentityMap = categoryVos.stream() Map<String, CategoryTreeVo> nameIdentityMap = categoryVos.stream()
.map(CategoryTreeVo::from) .map(CategoryTreeVo::from)
.collect(Collectors.toMap(categoryVo -> categoryVo.getMetadata().getName(), .collect(Collectors.toMap(categoryVo -> categoryVo.getMetadata().getName(),
@ -93,6 +93,7 @@ public class CategoryFinderImpl implements CategoryFinder {
} }
}); });
return listToTree(nameIdentityMap.values()); return listToTree(nameIdentityMap.values());
});
} }
static List<CategoryTreeVo> listToTree(Collection<CategoryTreeVo> list) { static List<CategoryTreeVo> listToTree(Collection<CategoryTreeVo> list) {

View File

@ -2,6 +2,7 @@ package run.halo.app.theme.finders.impl;
import java.time.Instant; import java.time.Instant;
import java.util.Comparator; import java.util.Comparator;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -37,14 +38,13 @@ public class CommentFinderImpl implements CommentFinder {
} }
@Override @Override
public CommentVo getByName(String name) { public Mono<CommentVo> getByName(String name) {
return client.fetch(Comment.class, name) return client.fetch(Comment.class, name)
.flatMap(this::toCommentVo) .flatMap(this::toCommentVo);
.block();
} }
@Override @Override
public ListResult<CommentVo> list(Ref ref, Integer page, Integer size) { public Mono<ListResult<CommentVo>> list(Ref ref, Integer page, Integer size) {
return client.list(Comment.class, fixedPredicate(ref), return client.list(Comment.class, fixedPredicate(ref),
defaultComparator(), defaultComparator(),
pageNullSafe(page), sizeNullSafe(size)) pageNullSafe(page), sizeNullSafe(size))
@ -55,11 +55,11 @@ public class CommentFinderImpl implements CommentFinder {
commentVos) commentVos)
) )
) )
.block(); .defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
} }
@Override @Override
public ListResult<ReplyVo> listReply(String commentName, Integer page, Integer size) { public Mono<ListResult<ReplyVo>> listReply(String commentName, Integer page, Integer size) {
Comparator<Reply> comparator = Comparator<Reply> comparator =
Comparator.comparing(reply -> reply.getMetadata().getCreationTimestamp()); Comparator.comparing(reply -> reply.getMetadata().getCreationTimestamp());
return client.list(Reply.class, return client.list(Reply.class,
@ -73,7 +73,7 @@ public class CommentFinderImpl implements CommentFinder {
.map(replyVos -> new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), .map(replyVos -> new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
replyVos)) replyVos))
) )
.block(); .defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
} }
private Mono<CommentVo> toCommentVo(Comment comment) { private Mono<CommentVo> toCommentVo(Comment comment) {
@ -81,6 +81,7 @@ public class CommentFinderImpl implements CommentFinder {
return Mono.just(CommentVo.from(comment)) return Mono.just(CommentVo.from(comment))
.flatMap(commentVo -> getOwnerInfo(owner) .flatMap(commentVo -> getOwnerInfo(owner)
.map(commentVo::withOwner) .map(commentVo::withOwner)
.defaultIfEmpty(commentVo)
); );
} }
@ -88,6 +89,7 @@ public class CommentFinderImpl implements CommentFinder {
return Mono.just(ReplyVo.from(reply)) return Mono.just(ReplyVo.from(reply))
.flatMap(replyVo -> getOwnerInfo(reply.getSpec().getOwner()) .flatMap(replyVo -> getOwnerInfo(reply.getSpec().getOwner())
.map(replyVo::withOwner) .map(replyVo::withOwner)
.defaultIfEmpty(replyVo)
); );
} }
@ -97,7 +99,7 @@ public class CommentFinderImpl implements CommentFinder {
} }
return client.fetch(User.class, owner.getName()) return client.fetch(User.class, owner.getName())
.map(OwnerInfo::from) .map(OwnerInfo::from)
.switchIfEmpty(Mono.just(OwnerInfo.ghostUser())); .defaultIfEmpty(OwnerInfo.ghostUser());
} }
private Predicate<Comment> fixedPredicate(Ref ref) { private Predicate<Comment> fixedPredicate(Ref ref) {

View File

@ -1,7 +1,8 @@
package run.halo.app.theme.finders.impl; package run.halo.app.theme.finders.impl;
import java.util.List; import java.util.List;
import java.util.Objects; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.User; import run.halo.app.core.extension.User;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.theme.finders.ContributorFinder; import run.halo.app.theme.finders.ContributorFinder;
@ -24,20 +25,17 @@ public class ContributorFinderImpl implements ContributorFinder {
} }
@Override @Override
public Contributor getContributor(String name) { public Mono<Contributor> getContributor(String name) {
return client.fetch(User.class, name) return client.fetch(User.class, name)
.map(Contributor::from) .map(Contributor::from);
.block();
} }
@Override @Override
public List<Contributor> getContributors(List<String> names) { public Flux<Contributor> getContributors(List<String> names) {
if (names == null) { if (names == null) {
return List.of(); return Flux.empty();
} }
return names.stream() return Flux.fromIterable(names)
.map(this::getContributor) .flatMap(this::getContributor);
.filter(Objects::nonNull)
.toList();
} }
} }

View File

@ -10,9 +10,10 @@ import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.comparator.Comparators; import org.springframework.util.comparator.Comparators;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Menu; import run.halo.app.core.extension.Menu;
import run.halo.app.core.extension.MenuItem; import run.halo.app.core.extension.MenuItem;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
@ -37,43 +38,45 @@ public class MenuFinderImpl implements MenuFinder {
private final SystemConfigurableEnvironmentFetcher environmentFetcher; private final SystemConfigurableEnvironmentFetcher environmentFetcher;
@Override @Override
public MenuVo getByName(String name) { public Mono<MenuVo> getByName(String name) {
return listAsTree().stream() return listAsTree()
.filter(menu -> menu.getMetadata().getName().equals(name)) .filter(menu -> menu.getMetadata().getName().equals(name))
.findAny() .next();
.orElse(null);
} }
@Override @Override
public MenuVo getPrimary() { public Mono<MenuVo> getPrimary() {
List<MenuVo> menuVos = listAsTree(); return listAsTree().collectList()
.flatMap(menuVos -> {
if (CollectionUtils.isEmpty(menuVos)) { if (CollectionUtils.isEmpty(menuVos)) {
return null; return Mono.empty();
} }
return environmentFetcher.fetch(SystemSetting.Menu.GROUP, SystemSetting.Menu.class) return environmentFetcher.fetch(SystemSetting.Menu.GROUP, SystemSetting.Menu.class)
.blockOptional()
.map(SystemSetting.Menu::getPrimary) .map(SystemSetting.Menu::getPrimary)
.filter(StringUtils::isNotBlank) .map(primaryConfig -> menuVos.stream()
.flatMap(primary -> menuVos.stream() .filter(menuVo -> menuVo.getMetadata().getName().equals(primaryConfig))
.filter(menuVo -> menuVo.getMetadata().getName().equals(primary)) .findAny()
.findAny()) .orElse(menuVos.get(0))
.orElse(menuVos.get(0)); )
.defaultIfEmpty(menuVos.get(0));
});
} }
List<MenuVo> listAll() { Flux<MenuVo> listAll() {
return client.list(Menu.class, null, null) return client.list(Menu.class, null, null)
.map(MenuVo::from) .map(MenuVo::from);
.collectList()
.block();
} }
List<MenuVo> listAsTree() { Flux<MenuVo> listAsTree() {
Collection<MenuItemVo> menuItemVos = populateParentName(listAllMenuItem()); return listAllMenuItem()
.collectList()
.map(MenuFinderImpl::populateParentName)
.flatMapMany(menuItemVos -> {
List<MenuItemVo> treeList = listToTree(menuItemVos); List<MenuItemVo> treeList = listToTree(menuItemVos);
Map<String, MenuItemVo> nameItemRootNodeMap = treeList.stream() Map<String, MenuItemVo> nameItemRootNodeMap = treeList.stream()
.collect(Collectors.toMap(item -> item.getMetadata().getName(), Function.identity())); .collect(Collectors.toMap(item -> item.getMetadata().getName(),
Function.identity()));
return listAll().stream() return listAll()
.map(menuVo -> { .map(menuVo -> {
LinkedHashSet<String> menuItemNames = menuVo.getSpec().getMenuItems(); LinkedHashSet<String> menuItemNames = menuVo.getSpec().getMenuItems();
if (menuItemNames == null) { if (menuItemNames == null) {
@ -85,8 +88,8 @@ public class MenuFinderImpl implements MenuFinder {
.sorted(defaultTreeNodeComparator()) .sorted(defaultTreeNodeComparator())
.toList(); .toList();
return menuVo.withMenuItems(menuItems); return menuVo.withMenuItems(menuItems);
}) });
.toList(); });
} }
static List<MenuItemVo> listToTree(Collection<MenuItemVo> list) { static List<MenuItemVo> listToTree(Collection<MenuItemVo> list) {
@ -109,11 +112,9 @@ public class MenuFinderImpl implements MenuFinder {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
List<MenuItemVo> listAllMenuItem() { Flux<MenuItemVo> listAllMenuItem() {
return client.list(MenuItem.class, null, null) return client.list(MenuItem.class, null, null)
.map(MenuItemVo::from) .map(MenuItemVo::from);
.collectList()
.block();
} }
static Comparator<MenuItemVo> defaultTreeNodeComparator() { static Comparator<MenuItemVo> defaultTreeNodeComparator() {

View File

@ -15,7 +15,10 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
import org.springframework.util.comparator.Comparators; import org.springframework.util.comparator.Comparators;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2; import reactor.util.function.Tuple2;
import run.halo.app.content.ContentService; import run.halo.app.content.ContentService;
import run.halo.app.core.extension.Counter; import run.halo.app.core.extension.Counter;
@ -30,15 +33,13 @@ import run.halo.app.theme.finders.ContributorFinder;
import run.halo.app.theme.finders.Finder; import run.halo.app.theme.finders.Finder;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
import run.halo.app.theme.finders.TagFinder; import run.halo.app.theme.finders.TagFinder;
import run.halo.app.theme.finders.vo.CategoryVo;
import run.halo.app.theme.finders.vo.ContentVo; import run.halo.app.theme.finders.vo.ContentVo;
import run.halo.app.theme.finders.vo.Contributor; import run.halo.app.theme.finders.vo.ListedPostVo;
import run.halo.app.theme.finders.vo.NavigationPostVo; import run.halo.app.theme.finders.vo.NavigationPostVo;
import run.halo.app.theme.finders.vo.PostArchiveVo; import run.halo.app.theme.finders.vo.PostArchiveVo;
import run.halo.app.theme.finders.vo.PostArchiveYearMonthVo; import run.halo.app.theme.finders.vo.PostArchiveYearMonthVo;
import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.finders.vo.PostVo;
import run.halo.app.theme.finders.vo.StatsVo; import run.halo.app.theme.finders.vo.StatsVo;
import run.halo.app.theme.finders.vo.TagVo;
/** /**
* A finder for {@link Post}. * A finder for {@link Post}.
@ -78,53 +79,49 @@ public class PostFinderImpl implements PostFinder {
} }
@Override @Override
public PostVo getByName(String postName) { public Mono<PostVo> getByName(String postName) {
Post post = client.fetch(Post.class, postName) return client.fetch(Post.class, postName)
.block(); .flatMap(this::getListedPostVo)
if (post == null) { .map(PostVo::from)
return null; .flatMap(postVo -> content(postName)
} .doOnNext(postVo::setContent)
PostVo postVo = getPostVo(post); .thenReturn(postVo)
postVo.setContent(content(postName)); );
return postVo;
} }
@Override @Override
public ContentVo content(String postName) { public Mono<ContentVo> content(String postName) {
return client.fetch(Post.class, postName) return client.fetch(Post.class, postName)
.map(post -> post.getSpec().getReleaseSnapshot()) .map(post -> post.getSpec().getReleaseSnapshot())
.flatMap(contentService::getContent) .flatMap(contentService::getContent)
.map(wrapper -> ContentVo.builder().content(wrapper.getContent()) .map(wrapper -> ContentVo.builder().content(wrapper.getContent())
.raw(wrapper.getRaw()).build()) .raw(wrapper.getRaw()).build());
.block();
} }
@Override @Override
public NavigationPostVo cursor(String currentName) { public Mono<NavigationPostVo> cursor(String currentName) {
// TODO Optimize the post names query here // TODO Optimize the post names query here
List<String> postNames = client.list(Post.class, FIXED_PREDICATE, defaultComparator()) return client.list(Post.class, FIXED_PREDICATE, defaultComparator())
.map(post -> post.getMetadata().getName()) .map(post -> post.getMetadata().getName())
.collectList() .collectList()
.block(); .flatMap(postNames -> Mono.just(NavigationPostVo.builder())
if (postNames == null) { .flatMap(builder -> getByName(currentName)
return NavigationPostVo.empty(); .doOnNext(builder::current)
} .thenReturn(builder)
)
NavigationPostVo.NavigationPostVoBuilder builder = NavigationPostVo.builder() .flatMap(builder -> {
.current(getByName(currentName)); Pair<String, String> previousNextPair =
postPreviousNextPair(postNames, currentName);
Pair<String, String> previousNextPair = postPreviousNextPair(postNames, currentName);
String previousPostName = previousNextPair.getLeft(); String previousPostName = previousNextPair.getLeft();
String nextPostName = previousNextPair.getRight(); String nextPostName = previousNextPair.getRight();
return getByName(previousPostName)
if (previousPostName != null) { .doOnNext(builder::previous)
builder.previous(getByName(previousPostName)); .then(getByName(nextPostName))
} .doOnNext(builder::next)
.thenReturn(builder);
if (nextPostName != null) { })
builder.next(getByName(nextPostName)); .map(NavigationPostVo.NavigationPostVoBuilder::build))
} .defaultIfEmpty(NavigationPostVo.empty());
return builder.build();
} }
static Pair<String, String> postPreviousNextPair(List<String> postNames, static Pair<String, String> postPreviousNextPair(List<String> postNames,
@ -201,36 +198,37 @@ public class PostFinderImpl implements PostFinder {
} }
@Override @Override
public ListResult<PostVo> list(Integer page, Integer size) { public Mono<ListResult<ListedPostVo>> list(Integer page, Integer size) {
return listPost(page, size, null, defaultComparator()); return listPost(page, size, null, defaultComparator());
} }
@Override @Override
public ListResult<PostVo> listByCategory(Integer page, Integer size, String categoryName) { public Mono<ListResult<ListedPostVo>> listByCategory(Integer page, Integer size,
String categoryName) {
return listPost(page, size, return listPost(page, size,
post -> contains(post.getSpec().getCategories(), categoryName), defaultComparator()); post -> contains(post.getSpec().getCategories(), categoryName), defaultComparator());
} }
@Override @Override
public ListResult<PostVo> listByTag(Integer page, Integer size, String tag) { public Mono<ListResult<ListedPostVo>> listByTag(Integer page, Integer size, String tag) {
return listPost(page, size, return listPost(page, size,
post -> contains(post.getSpec().getTags(), tag), defaultComparator()); post -> contains(post.getSpec().getTags(), tag), defaultComparator());
} }
@Override @Override
public ListResult<PostArchiveVo> archives(Integer page, Integer size) { public Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size) {
return archives(page, size, null, null); return archives(page, size, null, null);
} }
@Override @Override
public ListResult<PostArchiveVo> archives(Integer page, Integer size, String year) { public Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size, String year) {
return archives(page, size, year, null); return archives(page, size, year, null);
} }
@Override @Override
public ListResult<PostArchiveVo> archives(Integer page, Integer size, String year, public Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size, String year,
String month) { String month) {
ListResult<PostVo> list = listPost(page, size, post -> { return listPost(page, size, post -> {
Map<String, String> labels = post.getMetadata().getLabels(); Map<String, String> labels = post.getMetadata().getLabels();
if (labels == null) { if (labels == null) {
return false; return false;
@ -240,16 +238,16 @@ public class PostFinderImpl implements PostFinder {
boolean monthMatch = StringUtils.isBlank(month) boolean monthMatch = StringUtils.isBlank(month)
|| month.equals(labels.get(Post.ARCHIVE_MONTH_LABEL)); || month.equals(labels.get(Post.ARCHIVE_MONTH_LABEL));
return yearMatch && monthMatch; return yearMatch && monthMatch;
}, archiveComparator()); }, archiveComparator())
.map(list -> {
Map<String, List<PostVo>> yearPosts = list.get() Map<String, List<ListedPostVo>> yearPosts = list.get()
.collect(Collectors.groupingBy( .collect(Collectors.groupingBy(
post -> HaloUtils.getYearText(post.getSpec().getPublishTime()))); post -> HaloUtils.getYearText(post.getSpec().getPublishTime())));
List<PostArchiveVo> postArchives = List<PostArchiveVo> postArchives =
yearPosts.entrySet().stream().map(entry -> { yearPosts.entrySet().stream().map(entry -> {
String key = entry.getKey(); String key = entry.getKey();
// archives by month // archives by month
Map<String, List<PostVo>> monthPosts = entry.getValue().stream() Map<String, List<ListedPostVo>> monthPosts = entry.getValue().stream()
.collect(Collectors.groupingBy( .collect(Collectors.groupingBy(
post -> HaloUtils.getMonthText(post.getSpec().getPublishTime()))); post -> HaloUtils.getMonthText(post.getSpec().getPublishTime())));
// convert to archive year month value objects // convert to archive year month value objects
@ -267,7 +265,10 @@ public class PostFinderImpl implements PostFinder {
.months(monthArchives) .months(monthArchives)
.build(); .build();
}).toList(); }).toList();
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), postArchives); return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
postArchives);
})
.defaultIfEmpty(new ListResult<>(page, size, 0, List.of()));
} }
private boolean contains(List<String> c, String key) { private boolean contains(List<String> c, String key) {
@ -277,24 +278,29 @@ public class PostFinderImpl implements PostFinder {
return c.contains(key); return c.contains(key);
} }
private ListResult<PostVo> listPost(Integer page, Integer size, Predicate<Post> postPredicate, private Mono<ListResult<ListedPostVo>> listPost(Integer page, Integer size,
Predicate<Post> postPredicate,
Comparator<Post> comparator) { Comparator<Post> comparator) {
Predicate<Post> predicate = FIXED_PREDICATE Predicate<Post> predicate = FIXED_PREDICATE
.and(postPredicate == null ? post -> true : postPredicate); .and(postPredicate == null ? post -> true : postPredicate);
ListResult<Post> list = client.list(Post.class, predicate, return client.list(Post.class, predicate,
comparator, pageNullSafe(page), sizeNullSafe(size)) comparator, pageNullSafe(page), sizeNullSafe(size))
.block(); .flatMap(list -> Flux.fromStream(list.get())
if (list == null) { .flatMap(post -> getListedPostVo(post)
return new ListResult<>(List.of()); .map(postVo -> {
} populateStats(postVo);
List<PostVo> postVos = list.get() return postVo;
.map(this::getPostVo) })
.peek(this::populateStats) )
.toList(); .collectList()
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), postVos); .map(postVos -> new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
postVos)
)
)
.defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
} }
private void populateStats(PostVo postVo) { private <T extends ListedPostVo> void populateStats(T postVo) {
Counter counter = Counter counter =
counterService.getByName(MeterUtils.nameOf(Post.class, postVo.getMetadata() counterService.getByName(MeterUtils.nameOf(Post.class, postVo.getMetadata()
.getName())); .getName()));
@ -306,18 +312,45 @@ public class PostFinderImpl implements PostFinder {
postVo.setStats(statsVo); postVo.setStats(statsVo);
} }
private PostVo getPostVo(@NonNull Post post) { private Mono<ListedPostVo> getListedPostVo(@NonNull Post post) {
List<TagVo> tags = tagFinder.getByNames(post.getSpec().getTags()); ListedPostVo postVo = ListedPostVo.from(post);
List<CategoryVo> categoryVos = categoryFinder.getByNames(post.getSpec().getCategories()); postVo.setCategories(List.of());
List<Contributor> contributors = postVo.setTags(List.of());
contributorFinder.getContributors(post.getStatus().getContributors()); postVo.setContributors(List.of());
PostVo postVo = PostVo.from(post);
postVo.setCategories(categoryVos);
postVo.setTags(tags);
postVo.setContributors(contributors);
postVo.setOwner(contributorFinder.getContributor(post.getSpec().getOwner()));
populateStats(postVo); populateStats(postVo);
return postVo; return Mono.just(postVo)
.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() { static Comparator<Post> defaultComparator() {

View File

@ -7,6 +7,9 @@ import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.content.ContentService; import run.halo.app.content.ContentService;
import run.halo.app.core.extension.Counter; import run.halo.app.core.extension.Counter;
import run.halo.app.core.extension.Post; import run.halo.app.core.extension.Post;
@ -19,7 +22,7 @@ import run.halo.app.theme.finders.ContributorFinder;
import run.halo.app.theme.finders.Finder; import run.halo.app.theme.finders.Finder;
import run.halo.app.theme.finders.SinglePageFinder; import run.halo.app.theme.finders.SinglePageFinder;
import run.halo.app.theme.finders.vo.ContentVo; import run.halo.app.theme.finders.vo.ContentVo;
import run.halo.app.theme.finders.vo.Contributor; import run.halo.app.theme.finders.vo.ListedSinglePageVo;
import run.halo.app.theme.finders.vo.SinglePageVo; import run.halo.app.theme.finders.vo.SinglePageVo;
import run.halo.app.theme.finders.vo.StatsVo; import run.halo.app.theme.finders.vo.StatsVo;
@ -53,54 +56,56 @@ public class SinglePageFinderImpl implements SinglePageFinder {
} }
@Override @Override
public SinglePageVo getByName(String pageName) { public Mono<SinglePageVo> getByName(String pageName) {
SinglePage page = client.fetch(SinglePage.class, pageName) return client.fetch(SinglePage.class, pageName)
.block(); .map(page -> {
if (page == null) {
return null;
}
List<Contributor> contributors =
contributorFinder.getContributors(page.getStatus().getContributors());
SinglePageVo pageVo = SinglePageVo.from(page); SinglePageVo pageVo = SinglePageVo.from(page);
pageVo.setContributors(contributors); pageVo.setContributors(List.of());
pageVo.setContent(content(pageName)); pageVo.setContent(ContentVo.empty());
pageVo.setOwner(contributorFinder.getContributor(page.getSpec().getOwner()));
populateStats(pageVo); populateStats(pageVo);
return pageVo; return pageVo;
})
.flatMap(this::populateContributors)
.flatMap(page -> content(pageName)
.doOnNext(page::setContent)
.thenReturn(page)
)
.flatMap(page -> contributorFinder.getContributor(page.getSpec().getOwner())
.doOnNext(page::setOwner)
.thenReturn(page)
);
} }
@Override @Override
public ContentVo content(String pageName) { public Mono<ContentVo> content(String pageName) {
return client.fetch(SinglePage.class, pageName) return client.fetch(SinglePage.class, pageName)
.map(page -> page.getSpec().getReleaseSnapshot()) .map(page -> page.getSpec().getReleaseSnapshot())
.flatMap(contentService::getContent) .flatMap(contentService::getContent)
.map(wrapper -> ContentVo.builder().content(wrapper.getContent()) .map(wrapper -> ContentVo.builder().content(wrapper.getContent())
.raw(wrapper.getRaw()).build()) .raw(wrapper.getRaw()).build());
.block();
} }
@Override @Override
public ListResult<SinglePageVo> list(Integer page, Integer size) { public Mono<ListResult<ListedSinglePageVo>> list(Integer page, Integer size) {
ListResult<SinglePage> list = client.list(SinglePage.class, FIXED_PREDICATE, return client.list(SinglePage.class, FIXED_PREDICATE,
defaultComparator(), pageNullSafe(page), sizeNullSafe(size)) defaultComparator(), pageNullSafe(page), sizeNullSafe(size))
.block(); .flatMap(list -> Flux.fromStream(list.get())
if (list == null) { .map(singlePage -> {
return new ListResult<>(0, 0, 0, List.of()); ListedSinglePageVo pageVo = ListedSinglePageVo.from(singlePage);
} pageVo.setContributors(List.of());
List<SinglePageVo> pageVos = list.get()
.map(sp -> {
List<Contributor> contributors =
contributorFinder.getContributors(sp.getStatus().getContributors());
SinglePageVo pageVo = SinglePageVo.from(sp);
pageVo.setContributors(contributors);
populateStats(pageVo); populateStats(pageVo);
return pageVo; return pageVo;
}) })
.toList(); .flatMap(this::populateContributors)
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), pageVos); .collectList()
.map(pageVos -> new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
pageVos)
)
)
.defaultIfEmpty(new ListResult<>(0, 0, 0, List.of()));
} }
void populateStats(SinglePageVo pageVo) { <T extends ListedSinglePageVo> void populateStats(T pageVo) {
String name = pageVo.getMetadata().getName(); String name = pageVo.getMetadata().getName();
Counter counter = Counter counter =
counterService.getByName(MeterUtils.nameOf(SinglePage.class, name)); counterService.getByName(MeterUtils.nameOf(SinglePage.class, name));
@ -112,6 +117,17 @@ public class SinglePageFinderImpl implements SinglePageFinder {
pageVo.setStats(statsVo); pageVo.setStats(statsVo);
} }
<T extends ListedSinglePageVo> Mono<T> populateContributors(T pageVo) {
List<String> names = pageVo.getStatus().getContributors();
if (CollectionUtils.isEmpty(names)) {
return Mono.just(pageVo);
}
return contributorFinder.getContributors(names)
.collectList()
.doOnNext(pageVo::setContributors)
.thenReturn(pageVo);
}
static Comparator<SinglePage> defaultComparator() { static Comparator<SinglePage> defaultComparator() {
Function<SinglePage, Boolean> pinned = Function<SinglePage, Boolean> pinned =
page -> Objects.requireNonNullElse(page.getSpec().getPinned(), false); page -> Objects.requireNonNullElse(page.getSpec().getPinned(), false);

View File

@ -22,7 +22,7 @@ public class SiteStatsFinderImpl implements SiteStatsFinder {
private final ReactiveExtensionClient client; private final ReactiveExtensionClient client;
@Override @Override
public SiteStatsVo getStats() { public Mono<SiteStatsVo> getStats() {
return client.list(Counter.class, null, null) return client.list(Counter.class, null, null)
.reduce(SiteStatsVo.empty(), (stats, counter) -> { .reduce(SiteStatsVo.empty(), (stats, counter) -> {
stats.setVisit(stats.getVisit() + counter.getVisit()); stats.setVisit(stats.getVisit() + counter.getVisit());
@ -36,8 +36,7 @@ public class SiteStatsFinderImpl implements SiteStatsFinder {
) )
.flatMap(siteStatsVo -> categoryCount() .flatMap(siteStatsVo -> categoryCount()
.doOnNext(siteStatsVo::setCategory) .doOnNext(siteStatsVo::setCategory)
.thenReturn(siteStatsVo)) .thenReturn(siteStatsVo));
.block();
} }
Mono<Integer> postCount() { Mono<Integer> postCount() {

View File

@ -2,9 +2,10 @@ package run.halo.app.theme.finders.impl;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Tag; import run.halo.app.core.extension.Tag;
import run.halo.app.extension.ListResult; import run.halo.app.extension.ListResult;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
@ -31,21 +32,19 @@ public class TagFinderImpl implements TagFinder {
} }
@Override @Override
public TagVo getByName(String name) { public Mono<TagVo> getByName(String name) {
return client.fetch(Tag.class, name) return client.fetch(Tag.class, name)
.map(TagVo::from) .map(TagVo::from);
.block();
} }
@Override @Override
public List<TagVo> getByNames(List<String> names) { public Flux<TagVo> getByNames(List<String> names) {
return names.stream().map(this::getByName) return Flux.fromIterable(names)
.filter(Objects::nonNull) .flatMap(this::getByName);
.toList();
} }
@Override @Override
public ListResult<TagVo> list(Integer page, Integer size) { public Mono<ListResult<TagVo>> list(Integer page, Integer size) {
return client.list(Tag.class, null, return client.list(Tag.class, null,
DEFAULT_COMPARATOR.reversed(), pageNullSafe(page), sizeNullSafe(size)) DEFAULT_COMPARATOR.reversed(), pageNullSafe(page), sizeNullSafe(size))
.map(list -> { .map(list -> {
@ -54,16 +53,14 @@ public class TagFinderImpl implements TagFinder {
.collect(Collectors.toList()); .collect(Collectors.toList());
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), tagVos); return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), tagVos);
}) })
.block(); .defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
} }
@Override @Override
public List<TagVo> listAll() { public Flux<TagVo> listAll() {
return client.list(Tag.class, null, return client.list(Tag.class, null,
DEFAULT_COMPARATOR.reversed()) DEFAULT_COMPARATOR.reversed())
.map(TagVo::from) .map(TagVo::from);
.collectList()
.block();
} }
int pageNullSafe(Integer page) { int pageNullSafe(Integer page) {

View File

@ -34,19 +34,17 @@ public class ThemeFinderImpl implements ThemeFinder {
} }
@Override @Override
public ThemeVo activation() { public Mono<ThemeVo> activation() {
return environmentFetcher.fetch(SystemSetting.Theme.GROUP, SystemSetting.Theme.class) return environmentFetcher.fetch(SystemSetting.Theme.GROUP, SystemSetting.Theme.class)
.map(SystemSetting.Theme::getActive) .map(SystemSetting.Theme::getActive)
.flatMap(themeName -> client.fetch(Theme.class, themeName)) .flatMap(themeName -> client.fetch(Theme.class, themeName))
.flatMap(theme -> themeWithConfig(ThemeVo.from(theme))) .flatMap(theme -> themeWithConfig(ThemeVo.from(theme)));
.block();
} }
@Override @Override
public ThemeVo getByName(String themeName) { public Mono<ThemeVo> getByName(String themeName) {
return client.fetch(Theme.class, themeName) return client.fetch(Theme.class, themeName)
.flatMap(theme -> themeWithConfig(ThemeVo.from(theme))) .flatMap(theme -> themeWithConfig(ThemeVo.from(theme)));
.block();
} }
private Mono<ThemeVo> themeWithConfig(ThemeVo themeVo) { private Mono<ThemeVo> themeWithConfig(ThemeVo themeVo) {
@ -63,6 +61,6 @@ public class ThemeFinderImpl implements ThemeFinder {
JsonNode configJson = JsonUtils.mapToObject(config, JsonNode.class); JsonNode configJson = JsonUtils.mapToObject(config, JsonNode.class);
return themeVo.withConfig(configJson); return themeVo.withConfig(configJson);
}) })
.switchIfEmpty(Mono.just(themeVo)); .defaultIfEmpty(themeVo);
} }
} }

View File

@ -19,4 +19,14 @@ public class ContentVo {
String raw; String raw;
String content; String content;
/**
* Empty content object.
*/
public static ContentVo empty() {
return ContentVo.builder()
.raw("")
.content("")
.build();
}
} }

View File

@ -0,0 +1,59 @@
package run.halo.app.theme.finders.vo;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.springframework.util.Assert;
import run.halo.app.core.extension.Post;
import run.halo.app.extension.MetadataOperator;
/**
* A value object for {@link Post}.
*
* @author guqing
* @since 2.0.0
*/
@Data
@SuperBuilder
@ToString
@EqualsAndHashCode
public class ListedPostVo {
private MetadataOperator metadata;
private Post.PostSpec spec;
private Post.PostStatus status;
private List<CategoryVo> categories;
private List<TagVo> tags;
private List<Contributor> contributors;
private Contributor owner;
private StatsVo stats;
/**
* Convert {@link Post} to {@link ListedPostVo}.
*
* @param post post extension
* @return post value object
*/
public static ListedPostVo from(Post post) {
Assert.notNull(post, "The post must not be null.");
Post.PostSpec spec = post.getSpec();
Post.PostStatus postStatus = post.getStatusOrDefault();
return ListedPostVo.builder()
.metadata(post.getMetadata())
.spec(spec)
.status(postStatus)
.categories(List.of())
.tags(List.of())
.contributors(List.of())
.build();
}
}

View File

@ -0,0 +1,53 @@
package run.halo.app.theme.finders.vo;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.springframework.util.Assert;
import run.halo.app.core.extension.SinglePage;
import run.halo.app.extension.MetadataOperator;
/**
* A value object for {@link SinglePage}.
*
* @author guqing
* @since 2.0.0
*/
@Data
@SuperBuilder
@ToString
@EqualsAndHashCode
public class ListedSinglePageVo {
private MetadataOperator metadata;
private SinglePage.SinglePageSpec spec;
private SinglePage.SinglePageStatus status;
private StatsVo stats;
private List<Contributor> contributors;
private Contributor owner;
/**
* Convert {@link SinglePage} to {@link ListedSinglePageVo}.
*
* @param singlePage single page extension
* @return special page value object
*/
public static ListedSinglePageVo from(SinglePage singlePage) {
Assert.notNull(singlePage, "The singlePage must not be null.");
SinglePage.SinglePageSpec spec = singlePage.getSpec();
SinglePage.SinglePageStatus pageStatus = singlePage.getStatus();
return ListedSinglePageVo.builder()
.metadata(singlePage.getMetadata())
.spec(spec)
.status(pageStatus)
.contributors(List.of())
.build();
}
}

View File

@ -16,5 +16,5 @@ public class PostArchiveYearMonthVo {
String month; String month;
List<PostVo> posts; List<ListedPostVo> posts;
} }

View File

@ -1,13 +1,12 @@
package run.halo.app.theme.finders.vo; package run.halo.app.theme.finders.vo;
import java.util.List; import java.util.List;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import run.halo.app.core.extension.Post; import run.halo.app.core.extension.Post;
import run.halo.app.extension.MetadataOperator;
/** /**
* A value object for {@link Post}. * A value object for {@link Post}.
@ -16,29 +15,13 @@ import run.halo.app.extension.MetadataOperator;
* @since 2.0.0 * @since 2.0.0
*/ */
@Data @Data
@Builder @SuperBuilder
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode(callSuper = true)
public class PostVo { public class PostVo extends ListedPostVo {
private MetadataOperator metadata;
private Post.PostSpec spec;
private Post.PostStatus status;
private ContentVo content; private ContentVo content;
private List<CategoryVo> categories;
private List<TagVo> tags;
private List<Contributor> contributors;
private Contributor owner;
private StatsVo stats;
/** /**
* Convert {@link Post} to {@link PostVo}. * Convert {@link Post} to {@link PostVo}.
* *
@ -59,4 +42,21 @@ public class PostVo {
.content(new ContentVo(null, null)) .content(new ContentVo(null, null))
.build(); .build();
} }
/**
* Convert {@link Post} to {@link PostVo}.
*/
public static PostVo from(ListedPostVo postVo) {
return builder()
.metadata(postVo.getMetadata())
.spec(postVo.getSpec())
.status(postVo.getStatus())
.categories(postVo.getCategories())
.tags(postVo.getTags())
.contributors(postVo.getContributors())
.owner(postVo.getOwner())
.stats(postVo.getStats())
.content(new ContentVo("", ""))
.build();
}
} }

View File

@ -1,13 +1,12 @@
package run.halo.app.theme.finders.vo; package run.halo.app.theme.finders.vo;
import java.util.List; import java.util.List;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import run.halo.app.core.extension.SinglePage; import run.halo.app.core.extension.SinglePage;
import run.halo.app.extension.MetadataOperator;
/** /**
* A value object for {@link SinglePage}. * A value object for {@link SinglePage}.
@ -16,25 +15,13 @@ import run.halo.app.extension.MetadataOperator;
* @since 2.0.0 * @since 2.0.0
*/ */
@Data @Data
@Builder @SuperBuilder
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode(callSuper = true)
public class SinglePageVo { public class SinglePageVo extends ListedSinglePageVo {
private MetadataOperator metadata;
private SinglePage.SinglePageSpec spec;
private SinglePage.SinglePageStatus status;
private ContentVo content; private ContentVo content;
private StatsVo stats;
private List<Contributor> contributors;
private Contributor owner;
/** /**
* Convert {@link SinglePage} to {@link SinglePageVo}. * Convert {@link SinglePage} to {@link SinglePageVo}.
* *

View File

@ -13,8 +13,6 @@ import org.springframework.web.reactive.function.server.HandlerFunction;
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 reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.extension.ListResult;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.utils.PathUtils; import run.halo.app.infra.utils.PathUtils;
import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.DefaultTemplateEnum;
@ -47,7 +45,7 @@ public class ArchivesRouteStrategy implements ListPageRouteHandlerStrategy {
String path = request.path(); String path = request.path();
return environmentFetcher.fetchPost() return environmentFetcher.fetchPost()
.map(postSetting -> defaultIfNull(postSetting.getArchivePageSize(), DEFAULT_PAGE_SIZE)) .map(postSetting -> defaultIfNull(postSetting.getArchivePageSize(), DEFAULT_PAGE_SIZE))
.flatMap(pageSize -> listPost(pageNum(request), pageSize, year, month)) .flatMap(pageSize -> postFinder.archives(pageNum(request), pageSize, year, month))
.map(list -> new UrlContextListResult.Builder<PostArchiveVo>() .map(list -> new UrlContextListResult.Builder<PostArchiveVo>()
.listResult(list) .listResult(list)
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
@ -55,11 +53,6 @@ public class ArchivesRouteStrategy implements ListPageRouteHandlerStrategy {
.build()); .build());
} }
Mono<ListResult<PostArchiveVo>> listPost(int pageNum, int pageSize, String year, String month) {
return Mono.fromCallable(() -> postFinder.archives(pageNum, pageSize, year, month))
.subscribeOn(Schedulers.boundedElastic());
}
private String pathVariable(ServerRequest request, String name) { private String pathVariable(ServerRequest request, String name) {
Map<String, String> pathVariables = request.pathVariables(); Map<String, String> pathVariables = request.pathVariables();
if (pathVariables.containsKey(name)) { if (pathVariables.containsKey(name)) {

View File

@ -7,11 +7,8 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.CategoryFinder; import run.halo.app.theme.finders.CategoryFinder;
import run.halo.app.theme.finders.vo.CategoryTreeVo;
/** /**
* Categories router strategy for generate {@link HandlerFunction} specific to the template * Categories router strategy for generate {@link HandlerFunction} specific to the template
@ -25,16 +22,11 @@ import run.halo.app.theme.finders.vo.CategoryTreeVo;
public class CategoriesRouteStrategy implements ListPageRouteHandlerStrategy { public class CategoriesRouteStrategy implements ListPageRouteHandlerStrategy {
private final CategoryFinder categoryFinder; private final CategoryFinder categoryFinder;
private Mono<List<CategoryTreeVo>> categories() {
return Mono.defer(() -> Mono.just(categoryFinder.listAsTree()))
.publishOn(Schedulers.boundedElastic());
}
@Override @Override
public HandlerFunction<ServerResponse> getHandler() { public HandlerFunction<ServerResponse> getHandler() {
return request -> ServerResponse.ok() return request -> ServerResponse.ok()
.render(DefaultTemplateEnum.CATEGORIES.getValue(), .render(DefaultTemplateEnum.CATEGORIES.getValue(),
Map.of("categories", categories(), Map.of("categories", categoryFinder.listAsTree(),
ModelConst.TEMPLATE_ID, DefaultTemplateEnum.CATEGORIES.getValue())); ModelConst.TEMPLATE_ID, DefaultTemplateEnum.CATEGORIES.getValue()));
} }

View File

@ -12,17 +12,14 @@ import org.springframework.web.reactive.function.server.HandlerFunction;
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 reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.core.extension.Category; import run.halo.app.core.extension.Category;
import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.ListResult;
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.theme.DefaultTemplateEnum; import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.CategoryFinder; import run.halo.app.theme.finders.CategoryFinder;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
import run.halo.app.theme.finders.vo.CategoryVo; import run.halo.app.theme.finders.vo.ListedPostVo;
import run.halo.app.theme.finders.vo.PostVo;
import run.halo.app.theme.router.PageUrlUtils; import run.halo.app.theme.router.PageUrlUtils;
import run.halo.app.theme.router.UrlContextListResult; import run.halo.app.theme.router.UrlContextListResult;
import run.halo.app.theme.router.ViewNameResolver; import run.halo.app.theme.router.ViewNameResolver;
@ -46,29 +43,20 @@ public class CategoryRouteStrategy implements DetailsPageRouteHandlerStrategy {
private final SystemConfigurableEnvironmentFetcher environmentFetcher; private final SystemConfigurableEnvironmentFetcher environmentFetcher;
private Mono<UrlContextListResult<PostVo>> postListByCategoryName(String name, private Mono<UrlContextListResult<ListedPostVo>> postListByCategoryName(String name,
ServerRequest request) { ServerRequest request) {
String path = request.path(); String path = request.path();
return environmentFetcher.fetchPost() return environmentFetcher.fetchPost()
.map(post -> defaultIfNull(post.getCategoryPageSize(), ModelConst.DEFAULT_PAGE_SIZE)) .map(post -> defaultIfNull(post.getCategoryPageSize(), ModelConst.DEFAULT_PAGE_SIZE))
.flatMap(pageSize -> listPostsByCategory(pageNum(request), pageSize, name)) .flatMap(
.map(list -> new UrlContextListResult.Builder<PostVo>() pageSize -> postFinder.listByCategory(pageNum(request), pageSize, name))
.map(list -> new UrlContextListResult.Builder<ListedPostVo>()
.listResult(list) .listResult(list)
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
.prevUrl(PageUrlUtils.prevPageUrl(path)) .prevUrl(PageUrlUtils.prevPageUrl(path))
.build()); .build());
} }
private Mono<CategoryVo> categoryByName(String name) {
return Mono.fromCallable(() -> categoryFinder.getByName(name))
.subscribeOn(Schedulers.boundedElastic());
}
public Mono<ListResult<PostVo>> listPostsByCategory(int page, int size, String categoryName) {
return Mono.fromCallable(() -> postFinder.listByCategory(page, size, categoryName))
.subscribeOn(Schedulers.boundedElastic());
}
@Override @Override
public HandlerFunction<ServerResponse> getHandler(SystemSetting.ThemeRouteRules routeRules, public HandlerFunction<ServerResponse> getHandler(SystemSetting.ThemeRouteRules routeRules,
String name) { String name) {
@ -78,7 +66,7 @@ public class CategoryRouteStrategy implements DetailsPageRouteHandlerStrategy {
model.put("posts", postListByCategoryName(name, request)); model.put("posts", postListByCategoryName(name, request));
model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.CATEGORY.getValue()); model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.CATEGORY.getValue());
return categoryByName(name).flatMap(categoryVo -> { return categoryFinder.getByName(name).flatMap(categoryVo -> {
model.put("category", categoryVo); model.put("category", categoryVo);
String template = categoryVo.getSpec().getTemplate(); String template = categoryVo.getSpec().getTemplate();
return viewNameResolver.resolveViewNameOrDefault(request, template, return viewNameResolver.resolveViewNameOrDefault(request, template,

View File

@ -13,12 +13,10 @@ import org.springframework.web.reactive.function.server.HandlerFunction;
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 reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.extension.ListResult;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.finders.vo.ListedPostVo;
import run.halo.app.theme.router.PageUrlUtils; import run.halo.app.theme.router.PageUrlUtils;
import run.halo.app.theme.router.UrlContextListResult; import run.halo.app.theme.router.UrlContextListResult;
@ -36,23 +34,18 @@ public class IndexRouteStrategy implements ListPageRouteHandlerStrategy {
private final PostFinder postFinder; private final PostFinder postFinder;
private final SystemConfigurableEnvironmentFetcher environmentFetcher; private final SystemConfigurableEnvironmentFetcher environmentFetcher;
private Mono<UrlContextListResult<PostVo>> postList(ServerRequest request) { private Mono<UrlContextListResult<ListedPostVo>> postList(ServerRequest request) {
String path = request.path(); String path = request.path();
return environmentFetcher.fetchPost() return environmentFetcher.fetchPost()
.map(p -> defaultIfNull(p.getPostPageSize(), DEFAULT_PAGE_SIZE)) .map(p -> defaultIfNull(p.getPostPageSize(), DEFAULT_PAGE_SIZE))
.flatMap(pageSize -> listPost(pageNum(request), pageSize)) .flatMap(pageSize -> postFinder.list(pageNum(request), pageSize))
.map(list -> new UrlContextListResult.Builder<PostVo>() .map(list -> new UrlContextListResult.Builder<ListedPostVo>()
.listResult(list) .listResult(list)
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
.prevUrl(PageUrlUtils.prevPageUrl(path)) .prevUrl(PageUrlUtils.prevPageUrl(path))
.build()); .build());
} }
private Mono<ListResult<PostVo>> listPost(int page, int size) {
return Mono.fromCallable(() -> postFinder.list(page, size))
.subscribeOn(Schedulers.boundedElastic());
}
@Override @Override
public HandlerFunction<ServerResponse> getHandler() { public HandlerFunction<ServerResponse> getHandler() {
return request -> ServerResponse.ok() return request -> ServerResponse.ok()

View File

@ -8,15 +8,12 @@ import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser; import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.core.extension.Post; import run.halo.app.core.extension.Post;
import run.halo.app.extension.GVK; import run.halo.app.extension.GVK;
import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.GroupVersionKind;
import run.halo.app.infra.SystemSetting; import run.halo.app.infra.SystemSetting;
import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
import run.halo.app.theme.finders.vo.PostVo;
import run.halo.app.theme.router.ViewNameResolver; import run.halo.app.theme.router.ViewNameResolver;
/** /**
@ -57,7 +54,7 @@ public class PostRouteStrategy implements DetailsPageRouteHandlerStrategy {
model.put("plural", gvk.plural()); model.put("plural", gvk.plural());
// used by TemplateGlobalHeadProcessor and PostTemplateHeadProcessor // used by TemplateGlobalHeadProcessor and PostTemplateHeadProcessor
model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.POST.getValue()); model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.POST.getValue());
return postByName(name) return postFinder.getByName(name)
.flatMap(postVo -> { .flatMap(postVo -> {
model.put("post", postVo); model.put("post", postVo);
String template = postVo.getSpec().getTemplate(); String template = postVo.getSpec().getTemplate();
@ -72,9 +69,4 @@ public class PostRouteStrategy implements DetailsPageRouteHandlerStrategy {
public boolean supports(GroupVersionKind gvk) { public boolean supports(GroupVersionKind gvk) {
return groupVersionKind.equals(gvk); return groupVersionKind.equals(gvk);
} }
private Mono<PostVo> postByName(String name) {
return Mono.fromCallable(() -> postFinder.getByName(name))
.subscribeOn(Schedulers.boundedElastic());
}
} }

View File

@ -5,15 +5,12 @@ import java.util.Map;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.core.extension.SinglePage; import run.halo.app.core.extension.SinglePage;
import run.halo.app.extension.GVK; import run.halo.app.extension.GVK;
import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.GroupVersionKind;
import run.halo.app.infra.SystemSetting; import run.halo.app.infra.SystemSetting;
import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.SinglePageFinder; import run.halo.app.theme.finders.SinglePageFinder;
import run.halo.app.theme.finders.vo.SinglePageVo;
import run.halo.app.theme.router.ViewNameResolver; import run.halo.app.theme.router.ViewNameResolver;
/** /**
@ -40,11 +37,6 @@ public class SinglePageRouteStrategy implements DetailsPageRouteHandlerStrategy
return annotation.plural(); return annotation.plural();
} }
private Mono<SinglePageVo> singlePageByName(String name) {
return Mono.fromCallable(() -> singlePageFinder.getByName(name))
.subscribeOn(Schedulers.boundedElastic());
}
@Override @Override
public HandlerFunction<ServerResponse> getHandler(SystemSetting.ThemeRouteRules routeRules, public HandlerFunction<ServerResponse> getHandler(SystemSetting.ThemeRouteRules routeRules,
String name) { String name) {
@ -54,7 +46,7 @@ public class SinglePageRouteStrategy implements DetailsPageRouteHandlerStrategy
model.put("plural", getPlural()); model.put("plural", getPlural());
model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.SINGLE_PAGE.getValue()); model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.SINGLE_PAGE.getValue());
return singlePageByName(name).flatMap(singlePageVo -> { return singlePageFinder.getByName(name).flatMap(singlePageVo -> {
model.put("singlePage", singlePageVo); model.put("singlePage", singlePageVo);
String template = singlePageVo.getSpec().getTemplate(); String template = singlePageVo.getSpec().getTemplate();
return viewNameResolver.resolveViewNameOrDefault(request, template, return viewNameResolver.resolveViewNameOrDefault(request, template,

View File

@ -12,17 +12,14 @@ 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 reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.core.extension.Tag; import run.halo.app.core.extension.Tag;
import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.ListResult;
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.theme.DefaultTemplateEnum; import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
import run.halo.app.theme.finders.TagFinder; import run.halo.app.theme.finders.TagFinder;
import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.finders.vo.ListedPostVo;
import run.halo.app.theme.finders.vo.TagVo;
import run.halo.app.theme.router.PageUrlUtils; import run.halo.app.theme.router.PageUrlUtils;
import run.halo.app.theme.router.UrlContextListResult; import run.halo.app.theme.router.UrlContextListResult;
@ -43,28 +40,18 @@ public class TagRouteStrategy implements DetailsPageRouteHandlerStrategy {
private final SystemConfigurableEnvironmentFetcher environmentFetcher; private final SystemConfigurableEnvironmentFetcher environmentFetcher;
private Mono<UrlContextListResult<PostVo>> postList(ServerRequest request, String name) { private Mono<UrlContextListResult<ListedPostVo>> postList(ServerRequest request, String name) {
String path = request.path(); String path = request.path();
return environmentFetcher.fetchPost() return environmentFetcher.fetchPost()
.map(p -> defaultIfNull(p.getTagPageSize(), ModelConst.DEFAULT_PAGE_SIZE)) .map(p -> defaultIfNull(p.getTagPageSize(), ModelConst.DEFAULT_PAGE_SIZE))
.flatMap(pageSize -> listPostByTag(pageNum(request), pageSize, name)) .flatMap(pageSize -> postFinder.listByTag(pageNum(request), pageSize, name))
.map(list -> new UrlContextListResult.Builder<PostVo>() .map(list -> new UrlContextListResult.Builder<ListedPostVo>()
.listResult(list) .listResult(list)
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
.prevUrl(PageUrlUtils.prevPageUrl(path)) .prevUrl(PageUrlUtils.prevPageUrl(path))
.build()); .build());
} }
private Mono<ListResult<PostVo>> listPostByTag(int page, int size, String name) {
return Mono.fromCallable(() -> postFinder.listByTag(page, size, name))
.subscribeOn(Schedulers.boundedElastic());
}
private Mono<TagVo> tagByName(String name) {
return Mono.defer(() -> Mono.just(tagFinder.getByName(name)))
.publishOn(Schedulers.boundedElastic());
}
@Override @Override
public HandlerFunction<ServerResponse> getHandler(SystemSetting.ThemeRouteRules routeRules, public HandlerFunction<ServerResponse> getHandler(SystemSetting.ThemeRouteRules routeRules,
String name) { String name) {
@ -72,7 +59,7 @@ public class TagRouteStrategy implements DetailsPageRouteHandlerStrategy {
.render(DefaultTemplateEnum.TAG.getValue(), .render(DefaultTemplateEnum.TAG.getValue(),
Map.of("name", name, Map.of("name", name,
"posts", postList(request, name), "posts", postList(request, name),
"tag", tagByName(name), "tag", tagFinder.getByName(name),
ModelConst.TEMPLATE_ID, DefaultTemplateEnum.TAG.getValue() ModelConst.TEMPLATE_ID, DefaultTemplateEnum.TAG.getValue()
) )
); );

View File

@ -6,11 +6,8 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.TagFinder; import run.halo.app.theme.finders.TagFinder;
import run.halo.app.theme.finders.vo.TagVo;
/** /**
* The {@link TagsRouteStrategy} for generate {@link HandlerFunction} specific to the template * The {@link TagsRouteStrategy} for generate {@link HandlerFunction} specific to the template
@ -28,16 +25,11 @@ public class TagsRouteStrategy implements ListPageRouteHandlerStrategy {
this.tagFinder = tagFinder; this.tagFinder = tagFinder;
} }
private Mono<List<TagVo>> tags() {
return Mono.defer(() -> Mono.just(tagFinder.listAll()))
.publishOn(Schedulers.boundedElastic());
}
@Override @Override
public HandlerFunction<ServerResponse> getHandler() { public HandlerFunction<ServerResponse> getHandler() {
return request -> ServerResponse.ok() return request -> ServerResponse.ok()
.render(DefaultTemplateEnum.TAGS.getValue(), .render(DefaultTemplateEnum.TAGS.getValue(),
Map.of("tags", tags(), Map.of("tags", tagFinder.listAll(),
ModelConst.TEMPLATE_ID, DefaultTemplateEnum.TAGS.getValue() ModelConst.TEMPLATE_ID, DefaultTemplateEnum.TAGS.getValue()
) )
); );

View File

@ -142,7 +142,7 @@ class HaloProcessorDialectTest {
PostVo postVo = PostVo.builder() PostVo postVo = PostVo.builder()
.spec(postSpec) .spec(postSpec)
.metadata(metadata).build(); .metadata(metadata).build();
when(postFinder.getByName(eq("fake-post"))).thenReturn(postVo); when(postFinder.getByName(eq("fake-post"))).thenReturn(Mono.just(postVo));
SystemSetting.Basic basic = new SystemSetting.Basic(); SystemSetting.Basic basic = new SystemSetting.Basic();
basic.setFavicon(null); basic.setFavicon(null);

View File

@ -60,7 +60,7 @@ class CommentFinderEndpointTest {
@Test @Test
void listComments() { void listComments() {
when(commentFinder.list(any(), anyInt(), anyInt())) when(commentFinder.list(any(), anyInt(), anyInt()))
.thenReturn(new ListResult<>(1, 10, 0, List.of())); .thenReturn(Mono.just(new ListResult<>(1, 10, 0, List.of())));
Ref ref = new Ref(); Ref ref = new Ref();
ref.setGroup("content.halo.run"); ref.setGroup("content.halo.run");
@ -104,15 +104,13 @@ class CommentFinderEndpointTest {
@Test @Test
void listCommentReplies() { void listCommentReplies() {
when(commentFinder.listReply(any(), anyInt(), anyInt())) when(commentFinder.listReply(any(), anyInt(), anyInt()))
.thenReturn(new ListResult<>(2, 20, 0, List.of())); .thenReturn(Mono.just(new ListResult<>(2, 20, 0, List.of())));
webTestClient.get() webTestClient.get()
.uri(uriBuilder -> { .uri(uriBuilder -> uriBuilder.path("/comments/test-comment/reply")
return uriBuilder.path("/comments/test-comment/reply")
.queryParam("page", 2) .queryParam("page", 2)
.queryParam("size", 20) .queryParam("size", 20)
.build(); .build())
})
.exchange() .exchange()
.expectStatus() .expectStatus()
.isOk(); .isOk();

View File

@ -51,7 +51,7 @@ class CategoryFinderImplTest {
void getByName() throws JSONException { void getByName() throws JSONException {
when(client.fetch(eq(Category.class), eq("hello"))) when(client.fetch(eq(Category.class), eq("hello")))
.thenReturn(Mono.just(category())); .thenReturn(Mono.just(category()));
CategoryVo categoryVo = categoryFinder.getByName("hello"); CategoryVo categoryVo = categoryFinder.getByName("hello").block();
categoryVo.getMetadata().setCreationTimestamp(null); categoryVo.getMetadata().setCreationTimestamp(null);
JSONAssert.assertEquals(""" JSONAssert.assertEquals("""
{ {
@ -87,7 +87,7 @@ class CategoryFinderImplTest {
.toList()); .toList());
when(client.list(eq(Category.class), eq(null), any(), anyInt(), anyInt())) when(client.list(eq(Category.class), eq(null), any(), anyInt(), anyInt()))
.thenReturn(Mono.just(categories)); .thenReturn(Mono.just(categories));
ListResult<CategoryVo> list = categoryFinder.list(1, 10); ListResult<CategoryVo> list = categoryFinder.list(1, 10).block();
assertThat(list.getItems()).hasSize(3); assertThat(list.getItems()).hasSize(3);
assertThat(list.get().map(categoryVo -> categoryVo.getMetadata().getName()).toList()) assertThat(list.get().map(categoryVo -> categoryVo.getMetadata().getName()).toList())
.isEqualTo(List.of("c3", "c2", "hello")); .isEqualTo(List.of("c3", "c2", "hello"));
@ -97,7 +97,7 @@ class CategoryFinderImplTest {
void listAsTree() { void listAsTree() {
when(client.list(eq(Category.class), eq(null), any())) when(client.list(eq(Category.class), eq(null), any()))
.thenReturn(Flux.fromIterable(categoriesForTree())); .thenReturn(Flux.fromIterable(categoriesForTree()));
List<CategoryTreeVo> treeVos = categoryFinder.listAsTree(); List<CategoryTreeVo> treeVos = categoryFinder.listAsTree().collectList().block();
assertThat(treeVos).hasSize(1); assertThat(treeVos).hasSize(1);
} }
@ -110,7 +110,7 @@ class CategoryFinderImplTest {
void listAsTreeMore() { void listAsTreeMore() {
when(client.list(eq(Category.class), eq(null), any())) when(client.list(eq(Category.class), eq(null), any()))
.thenReturn(Flux.fromIterable(moreCategories())); .thenReturn(Flux.fromIterable(moreCategories()));
List<CategoryTreeVo> treeVos = categoryFinder.listAsTree(); List<CategoryTreeVo> treeVos = categoryFinder.listAsTree().collectList().block();
String s = visualizeTree(treeVos); String s = visualizeTree(treeVos);
assertThat(s).isEqualTo(""" assertThat(s).isEqualTo("""
(5) (5)

View File

@ -45,7 +45,7 @@ class MenuFinderImplTest {
Mockito.when(client.list(eq(MenuItem.class), eq(null), any())) Mockito.when(client.list(eq(MenuItem.class), eq(null), any()))
.thenReturn(Flux.fromIterable(tuple.getT2())); .thenReturn(Flux.fromIterable(tuple.getT2()));
List<MenuVo> menuVos = menuFinder.listAsTree(); List<MenuVo> menuVos = menuFinder.listAsTree().collectList().block();
assertThat(visualizeTree(menuVos)).isEqualTo(""" assertThat(visualizeTree(menuVos)).isEqualTo("""
D D
E E

View File

@ -18,6 +18,7 @@ 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.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.content.ContentService; import run.halo.app.content.ContentService;
import run.halo.app.content.ContentWrapper; import run.halo.app.content.ContentWrapper;
@ -78,7 +79,7 @@ class PostFinderImplTest {
.thenReturn(Mono.just(post)); .thenReturn(Mono.just(post));
when(contentService.getContent(post.getSpec().getReleaseSnapshot())) when(contentService.getContent(post.getSpec().getReleaseSnapshot()))
.thenReturn(Mono.just(contentWrapper)); .thenReturn(Mono.just(contentWrapper));
ContentVo content = postFinder.content("post-1"); ContentVo content = postFinder.content("post-1").block();
assertThat(content.getContent()).isEqualTo(contentWrapper.getContent()); assertThat(content.getContent()).isEqualTo(contentWrapper.getContent());
assertThat(content.getRaw()).isEqualTo(contentWrapper.getRaw()); assertThat(content.getRaw()).isEqualTo(contentWrapper.getRaw());
} }
@ -108,7 +109,12 @@ class PostFinderImplTest {
ListResult<Post> listResult = new ListResult<>(1, 10, 3, postsForArchives()); ListResult<Post> listResult = new ListResult<>(1, 10, 3, postsForArchives());
when(client.list(eq(Post.class), any(), any(), anyInt(), anyInt())) when(client.list(eq(Post.class), any(), any(), anyInt(), anyInt()))
.thenReturn(Mono.just(listResult)); .thenReturn(Mono.just(listResult));
ListResult<PostArchiveVo> archives = postFinder.archives(1, 10); 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();
List<PostArchiveVo> items = archives.getItems(); List<PostArchiveVo> items = archives.getItems();
assertThat(items.size()).isEqualTo(2); assertThat(items.size()).isEqualTo(2);
assertThat(items.get(0).getYear()).isEqualTo("2022"); assertThat(items.get(0).getYear()).isEqualTo("2022");

View File

@ -47,7 +47,7 @@ class TagFinderImplTest {
void getByName() throws JSONException { void getByName() throws JSONException {
when(client.fetch(eq(Tag.class), eq("t1"))) when(client.fetch(eq(Tag.class), eq("t1")))
.thenReturn(Mono.just(tag(1))); .thenReturn(Mono.just(tag(1)));
TagVo tagVo = tagFinder.getByName("t1"); TagVo tagVo = tagFinder.getByName("t1").block();
tagVo.getMetadata().setCreationTimestamp(null); tagVo.getMetadata().setCreationTimestamp(null);
JSONAssert.assertEquals(""" JSONAssert.assertEquals("""
{ {
@ -82,7 +82,7 @@ class TagFinderImplTest {
tags().stream().sorted(TagFinderImpl.DEFAULT_COMPARATOR.reversed()).toList() tags().stream().sorted(TagFinderImpl.DEFAULT_COMPARATOR.reversed()).toList()
) )
); );
List<TagVo> tags = tagFinder.listAll(); List<TagVo> tags = tagFinder.listAll().collectList().block();
assertThat(tags).hasSize(3); assertThat(tags).hasSize(3);
assertThat(tags.stream() assertThat(tags.stream()
.map(tag -> tag.getMetadata().getName()) .map(tag -> tag.getMetadata().getName())

View File

@ -1,8 +1,5 @@
package run.halo.app.theme.router.strategy; package run.halo.app.theme.router.strategy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import java.util.List; import java.util.List;
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;
@ -15,7 +12,6 @@ import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
import run.halo.app.theme.router.UrlContextListResult;
/** /**
* Tests for {@link ArchivesRouteStrategy}. * Tests for {@link ArchivesRouteStrategy}.
@ -31,12 +27,6 @@ class ArchivesRouteStrategyTest extends RouterStrategyTestSuite {
@InjectMocks @InjectMocks
private ArchivesRouteStrategy archivesRouteStrategy; private ArchivesRouteStrategy archivesRouteStrategy;
@Override
public void setUp() {
lenient().when(postFinder.list(any(), any())).thenReturn(
new UrlContextListResult<>(1, 10, 1, List.of(), null, null));
}
@Test @Test
void getRouteFunctionWhenDefaultPattern() { void getRouteFunctionWhenDefaultPattern() {
HandlerFunction<ServerResponse> handler = archivesRouteStrategy.getHandler(); HandlerFunction<ServerResponse> handler = archivesRouteStrategy.getHandler();

View File

@ -1,6 +1,6 @@
package run.halo.app.theme.router.strategy; package run.halo.app.theme.router.strategy;
import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -13,6 +13,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import run.halo.app.theme.finders.CategoryFinder; import run.halo.app.theme.finders.CategoryFinder;
/** /**
@ -29,12 +30,6 @@ class CategoriesRouteStrategyTest extends RouterStrategyTestSuite {
@InjectMocks @InjectMocks
private CategoriesRouteStrategy categoriesRouteStrategy; private CategoriesRouteStrategy categoriesRouteStrategy;
@Override
public void setUp() {
lenient().when(categoryFinder.listAsTree())
.thenReturn(List.of());
}
@Test @Test
void getRouteFunction() { void getRouteFunction() {
HandlerFunction<ServerResponse> handler = categoriesRouteStrategy.getHandler(); HandlerFunction<ServerResponse> handler = categoriesRouteStrategy.getHandler();
@ -47,6 +42,7 @@ class CategoriesRouteStrategyTest extends RouterStrategyTestSuite {
permalinkHttpGetRouter.insert(routerPath, handler); permalinkHttpGetRouter.insert(routerPath, handler);
} }
when(categoryFinder.listAsTree()).thenReturn(Flux.empty());
client.get() client.get()
.uri("/categories-test") .uri("/categories-test")
.exchange() .exchange()

View File

@ -1,10 +1,8 @@
package run.halo.app.theme.router.strategy; package run.halo.app.theme.router.strategy;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when;
import static org.mockito.Mockito.lenient;
import java.util.List;
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;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@ -14,7 +12,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.extension.ListResult; import reactor.core.publisher.Mono;
import run.halo.app.theme.finders.CategoryFinder; import run.halo.app.theme.finders.CategoryFinder;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
@ -36,12 +34,6 @@ class CategoryRouteStrategyTest extends RouterStrategyTestSuite {
@InjectMocks @InjectMocks
private CategoryRouteStrategy categoryRouteStrategy; private CategoryRouteStrategy categoryRouteStrategy;
@Override
public void setUp() {
lenient().when(postFinder.listByCategory(anyInt(), anyInt(), any()))
.thenReturn(new ListResult<>(1, 10, 0, List.of()));
}
@Test @Test
void getRouteFunction() { void getRouteFunction() {
RouterFunction<ServerResponse> routeFunction = getRouterFunction(); RouterFunction<ServerResponse> routeFunction = getRouterFunction();
@ -52,6 +44,8 @@ class CategoryRouteStrategyTest extends RouterStrategyTestSuite {
permalinkHttpGetRouter.insert("/categories-test/category-slug-2", permalinkHttpGetRouter.insert("/categories-test/category-slug-2",
categoryRouteStrategy.getHandler(null, "category-slug-2")); categoryRouteStrategy.getHandler(null, "category-slug-2"));
when(categoryFinder.getByName(any())).thenReturn(Mono.empty());
// /{prefix}/{slug} // /{prefix}/{slug}
client.get() client.get()
.uri("/categories-test/category-slug-1") .uri("/categories-test/category-slug-1")

View File

@ -1,8 +1,5 @@
package run.halo.app.theme.router.strategy; package run.halo.app.theme.router.strategy;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.lenient;
import java.util.List; import java.util.List;
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;
@ -14,7 +11,6 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.extension.ListResult;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
/** /**
@ -31,12 +27,6 @@ class IndexRouteStrategyTest extends RouterStrategyTestSuite {
@InjectMocks @InjectMocks
private IndexRouteStrategy indexRouteStrategy; private IndexRouteStrategy indexRouteStrategy;
@Override
public void setUp() {
lenient().when(postFinder.list(anyInt(), anyInt()))
.thenReturn(new ListResult<>(1, 10, 0, List.of()));
}
@Test @Test
void getRouteFunction() { void getRouteFunction() {
HandlerFunction<ServerResponse> handler = indexRouteStrategy.getHandler(); HandlerFunction<ServerResponse> handler = indexRouteStrategy.getHandler();

View File

@ -42,7 +42,8 @@ class PostRouteStrategyTest extends RouterStrategyTestSuite {
public void setUp() { public void setUp() {
lenient().when(viewNameResolver.resolveViewNameOrDefault(any(), any(), any())) lenient().when(viewNameResolver.resolveViewNameOrDefault(any(), any(), any()))
.thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue())); .thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue()));
lenient().when(postFinder.getByName(any())).thenReturn(PostVo.from(TestPost.postV1())); lenient().when(postFinder.getByName(any()))
.thenReturn(Mono.just(PostVo.from(TestPost.postV1())));
} }
@Test @Test

View File

@ -1,12 +1,11 @@
package run.halo.app.theme.router.strategy; package run.halo.app.theme.router.strategy;
import static org.mockito.ArgumentMatchers.any; 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.lenient; import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import static run.halo.app.theme.DefaultTemplateEnum.SINGLE_PAGE; import static run.halo.app.theme.DefaultTemplateEnum.SINGLE_PAGE;
import java.util.List;
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;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@ -16,7 +15,6 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.extension.ListResult;
import run.halo.app.theme.finders.SinglePageFinder; import run.halo.app.theme.finders.SinglePageFinder;
/** /**
@ -35,8 +33,6 @@ class SinglePageRouteStrategyTest extends RouterStrategyTestSuite {
@Override @Override
public void setUp() { public void setUp() {
lenient().when(singlePageFinder.list(anyInt(), anyInt()))
.thenReturn(new ListResult<>(1, 10, 0, List.of()));
lenient().when(viewResolver.resolveViewName(eq(SINGLE_PAGE.getValue()), any())) lenient().when(viewResolver.resolveViewName(eq(SINGLE_PAGE.getValue()), any()))
.thenReturn(Mono.just(new EmptyView())); .thenReturn(Mono.just(new EmptyView()));
} }
@ -53,6 +49,7 @@ class SinglePageRouteStrategyTest extends RouterStrategyTestSuite {
void shouldResponse200IfPermalinkFound() { void shouldResponse200IfPermalinkFound() {
permalinkHttpGetRouter.insert("/fake-slug", permalinkHttpGetRouter.insert("/fake-slug",
strategy.getHandler(getThemeRouteRules(), "fake-name")); strategy.getHandler(getThemeRouteRules(), "fake-name"));
when(singlePageFinder.getByName(any())).thenReturn(Mono.empty());
createClient().get() createClient().get()
.uri("/fake-slug") .uri("/fake-slug")
.exchange() .exchange()
@ -64,6 +61,8 @@ class SinglePageRouteStrategyTest extends RouterStrategyTestSuite {
void shouldResponse200IfSlugNameContainsSpecialChars() { void shouldResponse200IfSlugNameContainsSpecialChars() {
permalinkHttpGetRouter.insert("/fake / slug", permalinkHttpGetRouter.insert("/fake / slug",
strategy.getHandler(getThemeRouteRules(), "fake-name")); strategy.getHandler(getThemeRouteRules(), "fake-name"));
when(singlePageFinder.getByName(any())).thenReturn(Mono.empty());
createClient().get() createClient().get()
.uri("/fake / slug") .uri("/fake / slug")
.exchange() .exchange()
@ -74,6 +73,9 @@ class SinglePageRouteStrategyTest extends RouterStrategyTestSuite {
void shouldResponse200IfSlugNameContainsChineseChars() { void shouldResponse200IfSlugNameContainsChineseChars() {
permalinkHttpGetRouter.insert("/中文", permalinkHttpGetRouter.insert("/中文",
strategy.getHandler(getThemeRouteRules(), "fake-name")); strategy.getHandler(getThemeRouteRules(), "fake-name"));
when(singlePageFinder.getByName(any())).thenReturn(Mono.empty());
createClient().get() createClient().get()
.uri("/中文") .uri("/中文")
.exchange() .exchange()

View File

@ -1,10 +1,8 @@
package run.halo.app.theme.router.strategy; package run.halo.app.theme.router.strategy;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when;
import static org.mockito.Mockito.lenient;
import java.util.List;
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;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@ -14,8 +12,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.extension.ListResult; import reactor.core.publisher.Mono;
import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.PostFinder;
import run.halo.app.theme.finders.TagFinder;
/** /**
* Tests for {@link TagRouteStrategy}. * Tests for {@link TagRouteStrategy}.
@ -28,16 +27,12 @@ class TagRouteStrategyTest extends RouterStrategyTestSuite {
@Mock @Mock
private PostFinder postFinder; private PostFinder postFinder;
@Mock
private TagFinder tagFinder;
@InjectMocks @InjectMocks
private TagRouteStrategy tagRouteStrategy; private TagRouteStrategy tagRouteStrategy;
@Override
public void setUp() {
lenient().when(postFinder.listByTag(anyInt(), anyInt(), any()))
.thenReturn(new ListResult<>(1, 10, 0, List.of()));
}
@Test @Test
void getRouteFunction() { void getRouteFunction() {
RouterFunction<ServerResponse> routeFunction = getRouterFunction(); RouterFunction<ServerResponse> routeFunction = getRouterFunction();
@ -45,6 +40,7 @@ class TagRouteStrategyTest extends RouterStrategyTestSuite {
permalinkHttpGetRouter.insert("/tags-test/fake-slug", permalinkHttpGetRouter.insert("/tags-test/fake-slug",
tagRouteStrategy.getHandler(getThemeRouteRules(), "fake-name")); tagRouteStrategy.getHandler(getThemeRouteRules(), "fake-name"));
when(tagFinder.getByName(any())).thenReturn(Mono.empty());
client.get() client.get()
.uri("/tags-test/fake-slug") .uri("/tags-test/fake-slug")

View File

@ -1,6 +1,6 @@
package run.halo.app.theme.router.strategy; package run.halo.app.theme.router.strategy;
import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -13,6 +13,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import run.halo.app.theme.finders.TagFinder; import run.halo.app.theme.finders.TagFinder;
/** /**
@ -30,11 +31,6 @@ class TagsRouteStrategyTest extends RouterStrategyTestSuite {
@InjectMocks @InjectMocks
private TagsRouteStrategy tagsRouteStrategy; private TagsRouteStrategy tagsRouteStrategy;
@Override
public void setUp() {
lenient().when(tagFinder.listAll()).thenReturn(List.of());
}
@Test @Test
void getRouteFunction() { void getRouteFunction() {
RouterFunction<ServerResponse> routeFunction = getRouterFunction(); RouterFunction<ServerResponse> routeFunction = getRouterFunction();
@ -45,6 +41,7 @@ class TagsRouteStrategyTest extends RouterStrategyTestSuite {
for (String routerPath : routerPaths) { for (String routerPath : routerPaths) {
permalinkHttpGetRouter.insert(routerPath, handler); permalinkHttpGetRouter.insert(routerPath, handler);
} }
when(tagFinder.listAll()).thenReturn(Flux.empty());
client.get() client.get()
.uri("/tags-test") .uri("/tags-test")