mirror of https://github.com/halo-dev/halo
feat: support displaying private posts for owner on theme-side (#4412)
#### What type of PR is this? /kind feature /area core /milestone 2.9.x #### What this PR does / why we need it: 登录后支持在主题端展示作者的私有文章 how to test it? 1. 测试登录后是否能访问到自己创建的私有文章,退出登录后私有文章消失 2. 不能在在主题端看到别人创建的私有文章 3. 创建私有文章测试登录后使用主题端的上一页下一页功能是否正常 #### Which issue(s) this PR fixes: Fixes #3016 #### Does this PR introduce a user-facing change? ```release-note 登录后支持在主题端展示作者的私有文章 ```pull/4482/head
parent
5e21909e36
commit
637071b260
|
@ -1,7 +1,6 @@
|
||||||
package run.halo.app.theme.finders;
|
package run.halo.app.theme.finders;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -13,10 +12,6 @@ import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
import run.halo.app.theme.finders.vo.PostVo;
|
import run.halo.app.theme.finders.vo.PostVo;
|
||||||
|
|
||||||
public interface PostPublicQueryService {
|
public interface PostPublicQueryService {
|
||||||
Predicate<Post> FIXED_PREDICATE = post -> post.isPublished()
|
|
||||||
&& Objects.equals(false, post.getSpec().getDeleted())
|
|
||||||
&& Post.VisibleEnum.PUBLIC.equals(post.getSpec().getVisible());
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists posts page by predicate and comparator.
|
* Lists posts page by predicate and comparator.
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package run.halo.app.theme.finders.impl;
|
package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
import static run.halo.app.theme.finders.PostPublicQueryService.FIXED_PREDICATE;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -32,6 +30,7 @@ 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.router.ReactiveQueryPostPredicateResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A finder for {@link Post}.
|
* A finder for {@link Post}.
|
||||||
|
@ -43,17 +42,20 @@ import run.halo.app.theme.finders.vo.PostVo;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class PostFinderImpl implements PostFinder {
|
public class PostFinderImpl implements PostFinder {
|
||||||
|
|
||||||
|
|
||||||
private final ReactiveExtensionClient client;
|
private final ReactiveExtensionClient client;
|
||||||
|
|
||||||
private final PostPublicQueryService postPublicQueryService;
|
private final PostPublicQueryService postPublicQueryService;
|
||||||
|
|
||||||
|
private final ReactiveQueryPostPredicateResolver postPredicateResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<PostVo> getByName(String postName) {
|
public Mono<PostVo> getByName(String postName) {
|
||||||
return client.get(Post.class, postName)
|
return postPredicateResolver.getPredicate()
|
||||||
.filter(FIXED_PREDICATE)
|
.flatMap(predicate -> client.get(Post.class, postName)
|
||||||
.flatMap(post -> postPublicQueryService.convertToVo(post,
|
.filter(predicate)
|
||||||
post.getSpec().getReleaseSnapshot())
|
.flatMap(post -> postPublicQueryService.convertToVo(post,
|
||||||
|
post.getSpec().getReleaseSnapshot())
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +67,10 @@ public class PostFinderImpl implements PostFinder {
|
||||||
@Override
|
@Override
|
||||||
public Mono<NavigationPostVo> cursor(String currentName) {
|
public Mono<NavigationPostVo> cursor(String currentName) {
|
||||||
// TODO Optimize the post names query here
|
// TODO Optimize the post names query here
|
||||||
return client.list(Post.class, FIXED_PREDICATE, defaultComparator())
|
return postPredicateResolver.getPredicate()
|
||||||
|
.flatMapMany(postPredicate ->
|
||||||
|
client.list(Post.class, postPredicate, defaultComparator())
|
||||||
|
)
|
||||||
.map(post -> post.getMetadata().getName())
|
.map(post -> post.getMetadata().getName())
|
||||||
.collectList()
|
.collectList()
|
||||||
.flatMap(postNames -> Mono.just(NavigationPostVo.builder())
|
.flatMap(postNames -> Mono.just(NavigationPostVo.builder())
|
||||||
|
@ -98,7 +103,8 @@ public class PostFinderImpl implements PostFinder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<ListedPostVo> listAll() {
|
public Flux<ListedPostVo> listAll() {
|
||||||
return client.list(Post.class, FIXED_PREDICATE, defaultComparator())
|
return postPredicateResolver.getPredicate()
|
||||||
|
.flatMapMany(predicate -> client.list(Post.class, predicate, defaultComparator()))
|
||||||
.concatMap(postPublicQueryService::convertToListedVo);
|
.concatMap(postPublicQueryService::convertToListedVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import run.halo.app.theme.finders.vo.ContentVo;
|
||||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
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.router.ReactiveQueryPostPredicateResolver;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@ -48,13 +49,16 @@ public class PostPublicQueryServiceImpl implements PostPublicQueryService {
|
||||||
|
|
||||||
private final ExtensionGetter extensionGetter;
|
private final ExtensionGetter extensionGetter;
|
||||||
|
|
||||||
|
private final ReactiveQueryPostPredicateResolver postPredicateResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPostVo>> list(Integer page, Integer size,
|
public Mono<ListResult<ListedPostVo>> list(Integer page, Integer size,
|
||||||
Predicate<Post> postPredicate, Comparator<Post> comparator) {
|
Predicate<Post> postPredicate, Comparator<Post> comparator) {
|
||||||
Predicate<Post> predicate = FIXED_PREDICATE
|
return postPredicateResolver.getPredicate()
|
||||||
.and(postPredicate == null ? post -> true : postPredicate);
|
.map(predicate -> predicate.and(postPredicate == null ? post -> true : postPredicate))
|
||||||
return client.list(Post.class, predicate,
|
.flatMap(predicate -> client.list(Post.class, predicate,
|
||||||
comparator, pageNullSafe(page), sizeNullSafe(size))
|
comparator, pageNullSafe(page), sizeNullSafe(size))
|
||||||
|
)
|
||||||
.flatMap(list -> Flux.fromStream(list.get())
|
.flatMap(list -> Flux.fromStream(list.get())
|
||||||
.concatMap(post -> convertToListedVo(post)
|
.concatMap(post -> convertToListedVo(post)
|
||||||
.flatMap(postVo -> populateStats(postVo)
|
.flatMap(postVo -> populateStats(postVo)
|
||||||
|
@ -118,7 +122,6 @@ public class PostPublicQueryServiceImpl implements PostPublicQueryService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<PostVo> convertToVo(Post post, String snapshotName) {
|
public Mono<PostVo> convertToVo(Post post, String snapshotName) {
|
||||||
final String postName = post.getMetadata().getName();
|
|
||||||
final String baseSnapshotName = post.getSpec().getBaseSnapshot();
|
final String baseSnapshotName = post.getSpec().getBaseSnapshot();
|
||||||
return convertToListedVo(post)
|
return convertToListedVo(post)
|
||||||
.map(PostVo::from)
|
.map(PostVo::from)
|
||||||
|
@ -131,8 +134,10 @@ public class PostPublicQueryServiceImpl implements PostPublicQueryService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ContentVo> getContent(String postName) {
|
public Mono<ContentVo> getContent(String postName) {
|
||||||
return client.get(Post.class, postName)
|
return postPredicateResolver.getPredicate()
|
||||||
.filter(FIXED_PREDICATE)
|
.flatMap(predicate -> client.get(Post.class, postName)
|
||||||
|
.filter(predicate)
|
||||||
|
)
|
||||||
.flatMap(post -> {
|
.flatMap(post -> {
|
||||||
String releaseSnapshot = post.getSpec().getReleaseSnapshot();
|
String releaseSnapshot = post.getSpec().getReleaseSnapshot();
|
||||||
return postService.getContent(releaseSnapshot, post.getSpec().getBaseSnapshot())
|
return postService.getContent(releaseSnapshot, post.getSpec().getBaseSnapshot())
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.theme.finders.impl;
|
package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -10,12 +11,15 @@ import java.util.function.Predicate;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.infra.AnonymousUserConst;
|
||||||
import run.halo.app.theme.finders.Finder;
|
import run.halo.app.theme.finders.Finder;
|
||||||
import run.halo.app.theme.finders.SinglePageConversionService;
|
import run.halo.app.theme.finders.SinglePageConversionService;
|
||||||
import run.halo.app.theme.finders.SinglePageFinder;
|
import run.halo.app.theme.finders.SinglePageFinder;
|
||||||
|
@ -44,7 +48,7 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
||||||
@Override
|
@Override
|
||||||
public Mono<SinglePageVo> getByName(String pageName) {
|
public Mono<SinglePageVo> getByName(String pageName) {
|
||||||
return client.get(SinglePage.class, pageName)
|
return client.get(SinglePage.class, pageName)
|
||||||
.filter(FIXED_PREDICATE)
|
.filterWhen(page -> queryPredicate().map(predicate -> predicate.test(page)))
|
||||||
.flatMap(singlePagePublicQueryService::convertToVo);
|
.flatMap(singlePagePublicQueryService::convertToVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +82,25 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
||||||
.defaultIfEmpty(new ListResult<>(0, 0, 0, List.of()));
|
.defaultIfEmpty(new ListResult<>(0, 0, 0, List.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mono<Predicate<SinglePage>> queryPredicate() {
|
||||||
|
Predicate<SinglePage> predicate = page -> page.isPublished()
|
||||||
|
&& Objects.equals(false, page.getSpec().getDeleted());
|
||||||
|
Predicate<SinglePage> visiblePredicate =
|
||||||
|
page -> Post.VisibleEnum.PUBLIC.equals(page.getSpec().getVisible());
|
||||||
|
return currentUserName()
|
||||||
|
.map(username -> predicate.and(
|
||||||
|
visiblePredicate.or(page -> username.equals(page.getSpec().getOwner())))
|
||||||
|
)
|
||||||
|
.defaultIfEmpty(predicate.and(visiblePredicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
Mono<String> currentUserName() {
|
||||||
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
|
.map(SecurityContext::getAuthentication)
|
||||||
|
.map(Principal::getName)
|
||||||
|
.filter(name -> !AnonymousUserConst.isAnonymousUser(name));
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package run.halo.app.theme.router;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
import run.halo.app.extension.ExtensionUtil;
|
||||||
|
import run.halo.app.infra.AnonymousUserConst;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of {@link ReactiveQueryPostPredicateResolver}.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class DefaultQueryPostPredicateResolver implements ReactiveQueryPostPredicateResolver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Predicate<Post>> getPredicate() {
|
||||||
|
Predicate<Post> predicate = post -> post.isPublished()
|
||||||
|
&& !ExtensionUtil.isDeleted(post)
|
||||||
|
&& Objects.equals(false, post.getSpec().getDeleted());
|
||||||
|
Predicate<Post> visiblePredicate =
|
||||||
|
post -> Post.VisibleEnum.PUBLIC.equals(post.getSpec().getVisible());
|
||||||
|
return currentUserName()
|
||||||
|
.map(username -> predicate.and(
|
||||||
|
visiblePredicate.or(post -> username.equals(post.getSpec().getOwner())))
|
||||||
|
)
|
||||||
|
.defaultIfEmpty(predicate.and(visiblePredicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
Mono<String> currentUserName() {
|
||||||
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
|
.map(SecurityContext::getAuthentication)
|
||||||
|
.map(Principal::getName)
|
||||||
|
.filter(name -> !AnonymousUserConst.isAnonymousUser(name));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package run.halo.app.theme.router;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reactive query post predicate resolver.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
public interface ReactiveQueryPostPredicateResolver {
|
||||||
|
|
||||||
|
Mono<Predicate<Post>> getPredicate();
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import org.springframework.web.util.UriUtils;
|
import org.springframework.web.util.UriUtils;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -55,6 +56,10 @@ public class SinglePageRoute
|
||||||
|
|
||||||
private final ViewNameResolver viewNameResolver;
|
private final ViewNameResolver viewNameResolver;
|
||||||
|
|
||||||
|
private final TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
|
||||||
|
private final LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public Mono<HandlerFunction<ServerResponse>> route(@NonNull ServerRequest request) {
|
public Mono<HandlerFunction<ServerResponse>> route(@NonNull ServerRequest request) {
|
||||||
|
@ -144,6 +149,14 @@ public class SinglePageRoute
|
||||||
|
|
||||||
HandlerFunction<ServerResponse> handlerFunction(String name) {
|
HandlerFunction<ServerResponse> handlerFunction(String name) {
|
||||||
return request -> singlePageFinder.getByName(name)
|
return request -> singlePageFinder.getByName(name)
|
||||||
|
.doOnNext(singlePageVo -> {
|
||||||
|
titleVisibilityIdentifyCalculator.calculateTitle(
|
||||||
|
singlePageVo.getSpec().getTitle(),
|
||||||
|
singlePageVo.getSpec().getVisible(),
|
||||||
|
localeContextResolver.resolveLocaleContext(request.exchange())
|
||||||
|
.getLocale()
|
||||||
|
);
|
||||||
|
})
|
||||||
.flatMap(singlePageVo -> {
|
.flatMap(singlePageVo -> {
|
||||||
Map<String, Object> model = ModelMapUtils.singlePageModel(singlePageVo);
|
Map<String, Object> model = ModelMapUtils.singlePageModel(singlePageVo);
|
||||||
String template = singlePageVo.getSpec().getTemplate();
|
String template = singlePageVo.getSpec().getTemplate();
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package run.halo.app.theme.router;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TitleVisibilityIdentifyCalculator {
|
||||||
|
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate title with visibility identification.
|
||||||
|
*
|
||||||
|
* @param title title must not be null
|
||||||
|
* @param visibleEnum visibility enum
|
||||||
|
*/
|
||||||
|
public String calculateTitle(String title, Post.VisibleEnum visibleEnum, Locale locale) {
|
||||||
|
Assert.notNull(title, "Title must not be null");
|
||||||
|
if (Post.VisibleEnum.PRIVATE.equals(visibleEnum)) {
|
||||||
|
String identify = messageSource.getMessage(
|
||||||
|
"title.visibility.identification.private",
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
locale);
|
||||||
|
return title + identify;
|
||||||
|
}
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
|
@ -27,6 +28,7 @@ import run.halo.app.theme.finders.PostFinder;
|
||||||
import run.halo.app.theme.finders.vo.PostArchiveVo;
|
import run.halo.app.theme.finders.vo.PostArchiveVo;
|
||||||
import run.halo.app.theme.router.ModelConst;
|
import run.halo.app.theme.router.ModelConst;
|
||||||
import run.halo.app.theme.router.PageUrlUtils;
|
import run.halo.app.theme.router.PageUrlUtils;
|
||||||
|
import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator;
|
||||||
import run.halo.app.theme.router.UrlContextListResult;
|
import run.halo.app.theme.router.UrlContextListResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,6 +46,10 @@ public class ArchiveRouteFactory implements RouteFactory {
|
||||||
|
|
||||||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
|
||||||
|
private final TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
|
||||||
|
private final LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> create(String prefix) {
|
public RouterFunction<ServerResponse> create(String prefix) {
|
||||||
RequestPredicate requestPredicate = patterns(prefix).stream()
|
RequestPredicate requestPredicate = patterns(prefix).stream()
|
||||||
|
@ -83,6 +89,19 @@ public class ArchiveRouteFactory implements RouteFactory {
|
||||||
return configuredPageSize(environmentFetcher, SystemSetting.Post::getArchivePageSize)
|
return configuredPageSize(environmentFetcher, SystemSetting.Post::getArchivePageSize)
|
||||||
.flatMap(pageSize -> postFinder.archives(pageNum, pageSize, variables.getYear(),
|
.flatMap(pageSize -> postFinder.archives(pageNum, pageSize, variables.getYear(),
|
||||||
variables.getMonth()))
|
variables.getMonth()))
|
||||||
|
.doOnNext(list -> list.get()
|
||||||
|
.map(PostArchiveVo::getMonths)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.flatMap(month -> month.getPosts().stream())
|
||||||
|
.forEach(postVo -> postVo.getSpec()
|
||||||
|
.setTitle(titleVisibilityIdentifyCalculator.calculateTitle(
|
||||||
|
postVo.getSpec().getTitle(),
|
||||||
|
postVo.getSpec().getVisible(),
|
||||||
|
localeContextResolver.resolveLocaleContext(request.exchange())
|
||||||
|
.getLocale())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.map(list -> new UrlContextListResult.Builder<PostArchiveVo>()
|
.map(list -> new UrlContextListResult.Builder<PostArchiveVo>()
|
||||||
.listResult(list)
|
.listResult(list)
|
||||||
.nextUrl(PageUrlUtils.nextPageUrl(requestPath, totalPage(list)))
|
.nextUrl(PageUrlUtils.nextPageUrl(requestPath, totalPage(list)))
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Mono;
|
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;
|
||||||
|
@ -25,6 +26,7 @@ import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
import run.halo.app.theme.finders.vo.UserVo;
|
import run.halo.app.theme.finders.vo.UserVo;
|
||||||
import run.halo.app.theme.router.ModelConst;
|
import run.halo.app.theme.router.ModelConst;
|
||||||
import run.halo.app.theme.router.PageUrlUtils;
|
import run.halo.app.theme.router.PageUrlUtils;
|
||||||
|
import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator;
|
||||||
import run.halo.app.theme.router.UrlContextListResult;
|
import run.halo.app.theme.router.UrlContextListResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,6 +44,10 @@ public class AuthorPostsRouteFactory implements RouteFactory {
|
||||||
private final ReactiveExtensionClient client;
|
private final ReactiveExtensionClient client;
|
||||||
private SystemConfigurableEnvironmentFetcher environmentFetcher;
|
private SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
|
||||||
|
private final TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
|
||||||
|
private final LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> create(String pattern) {
|
public RouterFunction<ServerResponse> create(String pattern) {
|
||||||
return RouterFunctions
|
return RouterFunctions
|
||||||
|
@ -67,6 +73,17 @@ public class AuthorPostsRouteFactory implements RouteFactory {
|
||||||
int pageNum = pageNumInPathVariable(request);
|
int pageNum = pageNumInPathVariable(request);
|
||||||
return configuredPageSize(environmentFetcher, SystemSetting.Post::getPostPageSize)
|
return configuredPageSize(environmentFetcher, SystemSetting.Post::getPostPageSize)
|
||||||
.flatMap(pageSize -> postFinder.listByOwner(pageNum, pageSize, name))
|
.flatMap(pageSize -> postFinder.listByOwner(pageNum, pageSize, name))
|
||||||
|
.doOnNext(list -> {
|
||||||
|
list.getItems().forEach(listedPostVo -> {
|
||||||
|
listedPostVo.getSpec().setTitle(
|
||||||
|
titleVisibilityIdentifyCalculator.calculateTitle(
|
||||||
|
listedPostVo.getSpec().getTitle(),
|
||||||
|
listedPostVo.getSpec().getVisible(),
|
||||||
|
localeContextResolver.resolveLocaleContext(request.exchange())
|
||||||
|
.getLocale())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
.map(list -> new UrlContextListResult.Builder<ListedPostVo>()
|
.map(list -> new UrlContextListResult.Builder<ListedPostVo>()
|
||||||
.listResult(list)
|
.listResult(list)
|
||||||
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
|
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
@ -27,6 +28,7 @@ import run.halo.app.theme.finders.vo.CategoryVo;
|
||||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
import run.halo.app.theme.router.ModelConst;
|
import run.halo.app.theme.router.ModelConst;
|
||||||
import run.halo.app.theme.router.PageUrlUtils;
|
import run.halo.app.theme.router.PageUrlUtils;
|
||||||
|
import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator;
|
||||||
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;
|
||||||
|
|
||||||
|
@ -47,6 +49,10 @@ public class CategoryPostRouteFactory implements RouteFactory {
|
||||||
private final ReactiveExtensionClient client;
|
private final ReactiveExtensionClient client;
|
||||||
private final ViewNameResolver viewNameResolver;
|
private final ViewNameResolver viewNameResolver;
|
||||||
|
|
||||||
|
private final TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
|
||||||
|
private final LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> create(String prefix) {
|
public RouterFunction<ServerResponse> create(String prefix) {
|
||||||
return RouterFunctions.route(GET(PathUtils.combinePath(prefix, "/{slug}"))
|
return RouterFunctions.route(GET(PathUtils.combinePath(prefix, "/{slug}"))
|
||||||
|
@ -87,6 +93,15 @@ public class CategoryPostRouteFactory implements RouteFactory {
|
||||||
int pageNum = pageNumInPathVariable(request);
|
int pageNum = pageNumInPathVariable(request);
|
||||||
return configuredPageSize(environmentFetcher, SystemSetting.Post::getCategoryPageSize)
|
return configuredPageSize(environmentFetcher, SystemSetting.Post::getCategoryPageSize)
|
||||||
.flatMap(pageSize -> postFinder.listByCategory(pageNum, pageSize, name))
|
.flatMap(pageSize -> postFinder.listByCategory(pageNum, pageSize, name))
|
||||||
|
.doOnNext(list -> list.forEach(postVo -> postVo.getSpec().setTitle(
|
||||||
|
titleVisibilityIdentifyCalculator.calculateTitle(
|
||||||
|
postVo.getSpec().getTitle(),
|
||||||
|
postVo.getSpec().getVisible(),
|
||||||
|
localeContextResolver.resolveLocaleContext(request.exchange())
|
||||||
|
.getLocale()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
))
|
||||||
.map(list -> new UrlContextListResult.Builder<ListedPostVo>()
|
.map(list -> new UrlContextListResult.Builder<ListedPostVo>()
|
||||||
.listResult(list)
|
.listResult(list)
|
||||||
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
|
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
|
@ -21,6 +22,7 @@ import run.halo.app.theme.finders.PostFinder;
|
||||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
import run.halo.app.theme.router.ModelConst;
|
import run.halo.app.theme.router.ModelConst;
|
||||||
import run.halo.app.theme.router.PageUrlUtils;
|
import run.halo.app.theme.router.PageUrlUtils;
|
||||||
|
import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator;
|
||||||
import run.halo.app.theme.router.UrlContextListResult;
|
import run.halo.app.theme.router.UrlContextListResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,6 +38,8 @@ public class IndexRouteFactory implements RouteFactory {
|
||||||
|
|
||||||
private final PostFinder postFinder;
|
private final PostFinder postFinder;
|
||||||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
private final TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
private final LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> create(String pattern) {
|
public RouterFunction<ServerResponse> create(String pattern) {
|
||||||
|
@ -54,8 +58,19 @@ public class IndexRouteFactory implements RouteFactory {
|
||||||
|
|
||||||
private Mono<UrlContextListResult<ListedPostVo>> postList(ServerRequest request) {
|
private Mono<UrlContextListResult<ListedPostVo>> postList(ServerRequest request) {
|
||||||
String path = request.path();
|
String path = request.path();
|
||||||
|
|
||||||
return configuredPageSize(environmentFetcher, SystemSetting.Post::getPostPageSize)
|
return configuredPageSize(environmentFetcher, SystemSetting.Post::getPostPageSize)
|
||||||
.flatMap(pageSize -> postFinder.list(pageNumInPathVariable(request), pageSize))
|
.flatMap(pageSize -> postFinder.list(pageNumInPathVariable(request), pageSize))
|
||||||
|
.doOnNext(list -> list.getItems()
|
||||||
|
.forEach(listedPostVo -> listedPostVo.getSpec()
|
||||||
|
.setTitle(titleVisibilityIdentifyCalculator.calculateTitle(
|
||||||
|
listedPostVo.getSpec().getTitle(),
|
||||||
|
listedPostVo.getSpec().getVisible(),
|
||||||
|
localeContextResolver.resolveLocaleContext(request.exchange())
|
||||||
|
.getLocale())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.map(list -> new UrlContextListResult.Builder<ListedPostVo>()
|
.map(list -> new UrlContextListResult.Builder<ListedPostVo>()
|
||||||
.listResult(list)
|
.listResult(list)
|
||||||
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
|
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
|
||||||
|
|
|
@ -2,7 +2,6 @@ package run.halo.app.theme.router.factories;
|
||||||
|
|
||||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||||
import static run.halo.app.theme.finders.PostPublicQueryService.FIXED_PREDICATE;
|
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
@ -14,6 +13,7 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
@ -26,6 +26,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
@ -37,6 +38,8 @@ 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.PostVo;
|
||||||
import run.halo.app.theme.router.ModelMapUtils;
|
import run.halo.app.theme.router.ModelMapUtils;
|
||||||
|
import run.halo.app.theme.router.ReactiveQueryPostPredicateResolver;
|
||||||
|
import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator;
|
||||||
import run.halo.app.theme.router.ViewNameResolver;
|
import run.halo.app.theme.router.ViewNameResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,6 +59,12 @@ public class PostRouteFactory implements RouteFactory {
|
||||||
|
|
||||||
private final ReactiveExtensionClient client;
|
private final ReactiveExtensionClient client;
|
||||||
|
|
||||||
|
private final ReactiveQueryPostPredicateResolver queryPostPredicateResolver;
|
||||||
|
|
||||||
|
private final TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
|
||||||
|
private final LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> create(String pattern) {
|
public RouterFunction<ServerResponse> create(String pattern) {
|
||||||
PatternParser postParamPredicate =
|
PatternParser postParamPredicate =
|
||||||
|
@ -73,7 +82,7 @@ public class PostRouteFactory implements RouteFactory {
|
||||||
return request -> {
|
return request -> {
|
||||||
Map<String, String> variables = mergedVariables(request);
|
Map<String, String> variables = mergedVariables(request);
|
||||||
PostPatternVariable patternVariable = new PostPatternVariable();
|
PostPatternVariable patternVariable = new PostPatternVariable();
|
||||||
Optional.ofNullable(variables.get(paramPredicate.getQueryParamName()))
|
Optional.ofNullable(variables.get(paramPredicate.getParamName()))
|
||||||
.ifPresent(value -> {
|
.ifPresent(value -> {
|
||||||
switch (paramPredicate.getPlaceholderName()) {
|
switch (paramPredicate.getPlaceholderName()) {
|
||||||
case "name" -> patternVariable.setName(value);
|
case "name" -> patternVariable.setName(value);
|
||||||
|
@ -98,6 +107,15 @@ public class PostRouteFactory implements RouteFactory {
|
||||||
PostPatternVariable patternVariable) {
|
PostPatternVariable patternVariable) {
|
||||||
Mono<PostVo> postVoMono = bestMatchPost(patternVariable);
|
Mono<PostVo> postVoMono = bestMatchPost(patternVariable);
|
||||||
return postVoMono
|
return postVoMono
|
||||||
|
.doOnNext(postVo -> {
|
||||||
|
postVo.getSpec().setTitle(
|
||||||
|
titleVisibilityIdentifyCalculator.calculateTitle(
|
||||||
|
postVo.getSpec().getTitle(),
|
||||||
|
postVo.getSpec().getVisible(),
|
||||||
|
localeContextResolver.resolveLocaleContext(request.exchange())
|
||||||
|
.getLocale())
|
||||||
|
);
|
||||||
|
})
|
||||||
.flatMap(postVo -> {
|
.flatMap(postVo -> {
|
||||||
Map<String, Object> model = ModelMapUtils.postModel(postVo);
|
Map<String, Object> model = ModelMapUtils.postModel(postVo);
|
||||||
String template = postVo.getSpec().getTemplate();
|
String template = postVo.getSpec().getTemplate();
|
||||||
|
@ -133,16 +151,19 @@ public class PostRouteFactory implements RouteFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<Post> fetchPostsByName(String name) {
|
private Flux<Post> fetchPostsByName(String name) {
|
||||||
return client.fetch(Post.class, name)
|
return queryPostPredicateResolver.getPredicate()
|
||||||
.filter(FIXED_PREDICATE)
|
.flatMap(predicate -> client.fetch(Post.class, name)
|
||||||
|
.filter(predicate)
|
||||||
|
)
|
||||||
.flux();
|
.flux();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<Post> fetchPostsBySlug(String slug) {
|
private Flux<Post> fetchPostsBySlug(String slug) {
|
||||||
return client.list(Post.class,
|
return queryPostPredicateResolver.getPredicate()
|
||||||
post -> FIXED_PREDICATE.test(post)
|
.flatMapMany(predicate -> client.list(Post.class,
|
||||||
&& matchIfPresent(slug, post.getSpec().getSlug()),
|
predicate.and(post -> matchIfPresent(slug, post.getSpec().getSlug())),
|
||||||
null);
|
null)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean matchIfPresent(String variable, String target) {
|
private boolean matchIfPresent(String variable, String target) {
|
||||||
|
@ -175,6 +196,7 @@ public class PostRouteFactory implements RouteFactory {
|
||||||
return mergedVariables;
|
return mergedVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
static class PatternParser {
|
static class PatternParser {
|
||||||
private static final Pattern PATTERN_COMPILE = Pattern.compile("([^&?]*)=\\{(.*?)\\}(&|$)");
|
private static final Pattern PATTERN_COMPILE = Pattern.compile("([^&?]*)=\\{(.*?)\\}(&|$)");
|
||||||
private static final Cache<String, Matcher> MATCHER_CACHE = CacheBuilder.newBuilder()
|
private static final Cache<String, Matcher> MATCHER_CACHE = CacheBuilder.newBuilder()
|
||||||
|
@ -213,17 +235,5 @@ public class PostRouteFactory implements RouteFactory {
|
||||||
|
|
||||||
return RequestPredicates.queryParam(paramName, value -> true);
|
return RequestPredicates.queryParam(paramName, value -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPlaceholderName() {
|
|
||||||
return this.placeholderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQueryParamName() {
|
|
||||||
return this.paramName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isQueryParamPattern() {
|
|
||||||
return isQueryParamPattern;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
@ -25,6 +26,7 @@ import run.halo.app.theme.finders.TagFinder;
|
||||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
import run.halo.app.theme.finders.vo.TagVo;
|
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.TitleVisibilityIdentifyCalculator;
|
||||||
import run.halo.app.theme.router.UrlContextListResult;
|
import run.halo.app.theme.router.UrlContextListResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +45,10 @@ public class TagPostRouteFactory implements RouteFactory {
|
||||||
private final TagFinder tagFinder;
|
private final TagFinder tagFinder;
|
||||||
private final PostFinder postFinder;
|
private final PostFinder postFinder;
|
||||||
|
|
||||||
|
private final TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
|
||||||
|
private final LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> create(String prefix) {
|
public RouterFunction<ServerResponse> create(String prefix) {
|
||||||
return RouterFunctions
|
return RouterFunctions
|
||||||
|
@ -56,7 +62,17 @@ public class TagPostRouteFactory implements RouteFactory {
|
||||||
.flatMap(tagVo -> {
|
.flatMap(tagVo -> {
|
||||||
int pageNum = pageNumInPathVariable(request);
|
int pageNum = pageNumInPathVariable(request);
|
||||||
String path = request.path();
|
String path = request.path();
|
||||||
var postList = postList(tagVo.getMetadata().getName(), pageNum, path);
|
var postList = postList(tagVo.getMetadata().getName(), pageNum, path)
|
||||||
|
.doOnNext(list -> list.forEach(postVo ->
|
||||||
|
postVo.getSpec().setTitle(
|
||||||
|
titleVisibilityIdentifyCalculator.calculateTitle(
|
||||||
|
postVo.getSpec().getTitle(),
|
||||||
|
postVo.getSpec().getVisible(),
|
||||||
|
localeContextResolver.resolveLocaleContext(request.exchange())
|
||||||
|
.getLocale()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
));
|
||||||
return ServerResponse.ok()
|
return ServerResponse.ok()
|
||||||
.render(DefaultTemplateEnum.TAG.getValue(),
|
.render(DefaultTemplateEnum.TAG.getValue(),
|
||||||
Map.of("name", tagVo.getMetadata().getName(),
|
Map.of("name", tagVo.getMetadata().getName(),
|
||||||
|
|
|
@ -55,3 +55,5 @@ problemDetail.plugin.version.unsatisfied.requires=Plugin requires a minimum syst
|
||||||
problemDetail.plugin.missingManifest=Missing plugin manifest file "plugin.yaml" or manifest file does not conform to the specification.
|
problemDetail.plugin.missingManifest=Missing plugin manifest file "plugin.yaml" or manifest file does not conform to the specification.
|
||||||
problemDetail.internalServerError=Something went wrong, please try again later.
|
problemDetail.internalServerError=Something went wrong, please try again later.
|
||||||
problemDetail.migration.backup.notFound=The backup file does not exist or has been deleted.
|
problemDetail.migration.backup.notFound=The backup file does not exist or has been deleted.
|
||||||
|
|
||||||
|
title.visibility.identification.private=(Private)
|
|
@ -26,3 +26,5 @@ problemDetail.theme.version.unsatisfied.requires=主题要求一个最小的系
|
||||||
problemDetail.theme.install.missingManifest=缺少 theme.yaml 配置文件或配置文件不符合规范。
|
problemDetail.theme.install.missingManifest=缺少 theme.yaml 配置文件或配置文件不符合规范。
|
||||||
problemDetail.internalServerError=服务器内部发生错误,请稍候再试。
|
problemDetail.internalServerError=服务器内部发生错误,请稍候再试。
|
||||||
problemDetail.migration.backup.notFound=备份文件不存在或已删除。
|
problemDetail.migration.backup.notFound=备份文件不存在或已删除。
|
||||||
|
|
||||||
|
title.visibility.identification.private=(私有)
|
|
@ -4,13 +4,13 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static run.halo.app.theme.finders.PostPublicQueryService.FIXED_PREDICATE;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.apache.logging.log4j.util.Strings;
|
import org.apache.logging.log4j.util.Strings;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -32,6 +32,7 @@ import run.halo.app.theme.finders.TagFinder;
|
||||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
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.router.DefaultQueryPostPredicateResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link PostFinderImpl}.
|
* Tests for {@link PostFinderImpl}.
|
||||||
|
@ -77,7 +78,10 @@ class PostFinderImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void predicate() {
|
void predicate() {
|
||||||
List<String> strings = posts().stream().filter(FIXED_PREDICATE)
|
Predicate<Post> predicate = new DefaultQueryPostPredicateResolver().getPredicate().block();
|
||||||
|
assertThat(predicate).isNotNull();
|
||||||
|
|
||||||
|
List<String> strings = posts().stream().filter(predicate)
|
||||||
.map(post -> post.getMetadata().getName())
|
.map(post -> post.getMetadata().getName())
|
||||||
.toList();
|
.toList();
|
||||||
assertThat(strings).isEqualTo(List.of("post-1", "post-2", "post-6"));
|
assertThat(strings).isEqualTo(List.of("post-1", "post-2", "post-6"));
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package run.halo.app.theme.router;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
import run.halo.app.extension.Metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ReactiveQueryPostPredicateResolver}.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.9.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
class ReactiveQueryPostPredicateResolverTest {
|
||||||
|
|
||||||
|
private ReactiveQueryPostPredicateResolver postPredicateResolver;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
postPredicateResolver = new DefaultQueryPostPredicateResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPredicateWithoutAuth() {
|
||||||
|
postPredicateResolver.getPredicate()
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
.consumeNextWith(predicate -> {
|
||||||
|
Post post = new Post();
|
||||||
|
post.setMetadata(new Metadata());
|
||||||
|
post.getMetadata().setName("fake-post");
|
||||||
|
|
||||||
|
post.setSpec(new Post.PostSpec());
|
||||||
|
post.getSpec().setDeleted(false);
|
||||||
|
post.getMetadata().setLabels(Map.of(Post.PUBLISHED_LABEL, "true"));
|
||||||
|
post.getSpec().setVisible(Post.VisibleEnum.PRIVATE);
|
||||||
|
assertThat(predicate.test(post)).isFalse();
|
||||||
|
|
||||||
|
post.getSpec().setVisible(Post.VisibleEnum.PUBLIC);
|
||||||
|
assertThat(predicate.test(post)).isTrue();
|
||||||
|
|
||||||
|
post.getMetadata().setLabels(Map.of(Post.PUBLISHED_LABEL, "false"));
|
||||||
|
assertThat(predicate.test(post)).isFalse();
|
||||||
|
})
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "halo")
|
||||||
|
void getPredicateWithAuth() {
|
||||||
|
postPredicateResolver.getPredicate()
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
.consumeNextWith(predicate -> {
|
||||||
|
Post post = new Post();
|
||||||
|
post.setMetadata(new Metadata());
|
||||||
|
post.getMetadata().setName("fake-post");
|
||||||
|
|
||||||
|
post.setSpec(new Post.PostSpec());
|
||||||
|
post.getSpec().setDeleted(false);
|
||||||
|
post.getSpec().setOwner("halo");
|
||||||
|
post.getMetadata().setLabels(Map.of(Post.PUBLISHED_LABEL, "true"));
|
||||||
|
post.getSpec().setVisible(Post.VisibleEnum.PRIVATE);
|
||||||
|
assertThat(predicate.test(post)).isTrue();
|
||||||
|
|
||||||
|
post.getSpec().setOwner("guqing");
|
||||||
|
assertThat(predicate.test(post)).isFalse();
|
||||||
|
|
||||||
|
post.getSpec().setOwner("halo");
|
||||||
|
post.getSpec().setVisible(Post.VisibleEnum.PUBLIC);
|
||||||
|
assertThat(predicate.test(post)).isTrue();
|
||||||
|
|
||||||
|
post.getSpec().setDeleted(true);
|
||||||
|
assertThat(predicate.test(post)).isFalse();
|
||||||
|
|
||||||
|
post.getSpec().setVisible(Post.VisibleEnum.INTERNAL);
|
||||||
|
assertThat(predicate.test(post)).isFalse();
|
||||||
|
})
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import static org.mockito.Mockito.when;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
|
@ -21,6 +22,7 @@ import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.context.i18n.SimpleLocaleContext;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
@ -35,6 +37,7 @@ import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import org.springframework.web.util.UriUtils;
|
import org.springframework.web.util.UriUtils;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
@ -69,6 +72,12 @@ class SinglePageRouteTest {
|
||||||
@Mock
|
@Mock
|
||||||
ExtensionClient client;
|
ExtensionClient client;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
SinglePageRoute singlePageRoute;
|
SinglePageRoute singlePageRoute;
|
||||||
|
|
||||||
|
@ -115,6 +124,8 @@ class SinglePageRouteTest {
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
when(localeContextResolver.resolveLocaleContext(any()))
|
||||||
|
.thenReturn(new SimpleLocaleContext(Locale.getDefault()));
|
||||||
webTestClient.get()
|
webTestClient.get()
|
||||||
.uri("/archives/fake-name")
|
.uri("/archives/fake-name")
|
||||||
.exchange()
|
.exchange()
|
||||||
|
|
|
@ -5,17 +5,20 @@ import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
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;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.context.i18n.SimpleLocaleContext;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
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 org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.content.TestPost;
|
import run.halo.app.content.TestPost;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
@ -25,8 +28,11 @@ import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
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.PostVo;
|
||||||
|
import run.halo.app.theme.router.DefaultQueryPostPredicateResolver;
|
||||||
import run.halo.app.theme.router.EmptyView;
|
import run.halo.app.theme.router.EmptyView;
|
||||||
import run.halo.app.theme.router.ModelConst;
|
import run.halo.app.theme.router.ModelConst;
|
||||||
|
import run.halo.app.theme.router.ReactiveQueryPostPredicateResolver;
|
||||||
|
import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator;
|
||||||
import run.halo.app.theme.router.ViewNameResolver;
|
import run.halo.app.theme.router.ViewNameResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,6 +53,15 @@ class PostRouteFactoryTest extends RouteFactoryTestSuite {
|
||||||
@Mock
|
@Mock
|
||||||
private ReactiveExtensionClient client;
|
private ReactiveExtensionClient client;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ReactiveQueryPostPredicateResolver predicateResolver;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LocaleContextResolver localeContextResolver;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TitleVisibilityIdentifyCalculator titleVisibilityIdentifyCalculator;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private PostRouteFactory postRouteFactory;
|
private PostRouteFactory postRouteFactory;
|
||||||
|
|
||||||
|
@ -64,10 +79,14 @@ class PostRouteFactoryTest extends RouteFactoryTestSuite {
|
||||||
|
|
||||||
when(viewNameResolver.resolveViewNameOrDefault(any(), any(), any()))
|
when(viewNameResolver.resolveViewNameOrDefault(any(), any(), any()))
|
||||||
.thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue()));
|
.thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue()));
|
||||||
|
when(predicateResolver.getPredicate())
|
||||||
|
.thenReturn(new DefaultQueryPostPredicateResolver().getPredicate());
|
||||||
|
|
||||||
RouterFunction<ServerResponse> routerFunction = postRouteFactory.create("/archives/{name}");
|
RouterFunction<ServerResponse> routerFunction = postRouteFactory.create("/archives/{name}");
|
||||||
WebTestClient webTestClient = getWebTestClient(routerFunction);
|
WebTestClient webTestClient = getWebTestClient(routerFunction);
|
||||||
|
|
||||||
|
when(localeContextResolver.resolveLocaleContext(any()))
|
||||||
|
.thenReturn(new SimpleLocaleContext(Locale.getDefault()));
|
||||||
when(viewResolver.resolveViewName(any(), any()))
|
when(viewResolver.resolveViewName(any(), any()))
|
||||||
.thenReturn(Mono.just(new EmptyView() {
|
.thenReturn(Mono.just(new EmptyView() {
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue