mirror of https://github.com/halo-dev/halo
feat: provides the route of the post archive page for theme-side (#2598)
#### What type of PR is this? /kind feature /area core /milestone 2.0 #### What this PR does / why we need it: 提供文章归档页 #### Which issue(s) this PR fixes: Fixes #2548 #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note 为主题提供文章归档页 ```pull/2626/head
parent
95f0809042
commit
160dd909cc
|
@ -35,6 +35,10 @@ public class Post extends AbstractExtension {
|
|||
public static final String VISIBLE_LABEL = "content.halo.run/visible";
|
||||
public static final String PHASE_LABEL = "content.halo.run/phase";
|
||||
|
||||
public static final String ARCHIVE_YEAR_LABEL = "content.halo.run/archive-year";
|
||||
|
||||
public static final String ARCHIVE_MONTH_LABEL = "content.halo.run/archive-month";
|
||||
|
||||
@Schema(required = true)
|
||||
private PostSpec spec;
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import run.halo.app.extension.Ref;
|
|||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.infra.Condition;
|
||||
import run.halo.app.infra.ConditionStatus;
|
||||
import run.halo.app.infra.utils.HaloUtils;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
|
@ -90,6 +91,11 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
labels.put(Post.VISIBLE_LABEL,
|
||||
Objects.requireNonNullElse(spec.getVisible(), Post.VisibleEnum.PUBLIC).name());
|
||||
labels.put(Post.OWNER_LABEL, spec.getOwner());
|
||||
Instant publishTime = post.getSpec().getPublishTime();
|
||||
if (publishTime != null) {
|
||||
labels.put(Post.ARCHIVE_YEAR_LABEL, HaloUtils.getYearText(publishTime));
|
||||
labels.put(Post.ARCHIVE_MONTH_LABEL, HaloUtils.getMonthText(publishTime));
|
||||
}
|
||||
|
||||
if (!oldPost.equals(post)) {
|
||||
client.update(post);
|
||||
|
|
|
@ -3,10 +3,13 @@ package run.halo.app.infra.utils;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
|
||||
|
@ -50,4 +53,15 @@ public class HaloUtils {
|
|||
}
|
||||
return StringUtils.defaultString(userAgent, "unknown");
|
||||
}
|
||||
|
||||
public static String getMonthText(Instant instant) {
|
||||
Assert.notNull(instant, "Instant must not be null");
|
||||
int monthValue = instant.atZone(ZoneId.systemDefault()).getMonthValue();
|
||||
return StringUtils.leftPad(String.valueOf(monthValue), 2, '0');
|
||||
}
|
||||
|
||||
public static String getYearText(Instant instant) {
|
||||
Assert.notNull(instant, "Instant must not be null");
|
||||
return String.valueOf(instant.atZone(ZoneId.systemDefault()).getYear());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.springframework.lang.Nullable;
|
|||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.theme.finders.vo.ContentVo;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveVo;
|
||||
import run.halo.app.theme.finders.vo.PostVo;
|
||||
|
||||
/**
|
||||
|
@ -24,4 +25,10 @@ public interface PostFinder {
|
|||
String categoryName);
|
||||
|
||||
ListResult<PostVo> listByTag(@Nullable Integer page, @Nullable Integer size, String tag);
|
||||
|
||||
ListResult<PostArchiveVo> archives(Integer page, Integer size);
|
||||
|
||||
ListResult<PostArchiveVo> archives(Integer page, Integer size, String year);
|
||||
|
||||
ListResult<PostArchiveVo> archives(Integer page, Integer size, String year, String month);
|
||||
}
|
||||
|
|
|
@ -3,17 +3,21 @@ package run.halo.app.theme.finders.impl;
|
|||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.core.extension.Counter;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.utils.HaloUtils;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
import run.halo.app.theme.finders.CategoryFinder;
|
||||
|
@ -24,6 +28,8 @@ 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.Contributor;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveVo;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveYearMonthVo;
|
||||
import run.halo.app.theme.finders.vo.PostVo;
|
||||
import run.halo.app.theme.finders.vo.StatsVo;
|
||||
import run.halo.app.theme.finders.vo.TagVo;
|
||||
|
@ -90,19 +96,72 @@ public class PostFinderImpl implements PostFinder {
|
|||
|
||||
@Override
|
||||
public ListResult<PostVo> list(Integer page, Integer size) {
|
||||
return listPost(page, size, null);
|
||||
return listPost(page, size, null, defaultComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResult<PostVo> listByCategory(Integer page, Integer size, String categoryName) {
|
||||
return listPost(page, size,
|
||||
post -> contains(post.getSpec().getCategories(), categoryName));
|
||||
post -> contains(post.getSpec().getCategories(), categoryName), defaultComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResult<PostVo> listByTag(Integer page, Integer size, String tag) {
|
||||
return listPost(page, size,
|
||||
post -> contains(post.getSpec().getTags(), tag));
|
||||
post -> contains(post.getSpec().getTags(), tag), defaultComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResult<PostArchiveVo> archives(Integer page, Integer size) {
|
||||
return archives(page, size, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResult<PostArchiveVo> archives(Integer page, Integer size, String year) {
|
||||
return archives(page, size, year, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResult<PostArchiveVo> archives(Integer page, Integer size, String year,
|
||||
String month) {
|
||||
ListResult<PostVo> list = listPost(page, size, post -> {
|
||||
Map<String, String> labels = post.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
return false;
|
||||
}
|
||||
boolean yearMatch = StringUtils.isBlank(year)
|
||||
|| year.equals(labels.get(Post.ARCHIVE_YEAR_LABEL));
|
||||
boolean monthMatch = StringUtils.isBlank(month)
|
||||
|| month.equals(labels.get(Post.ARCHIVE_MONTH_LABEL));
|
||||
return yearMatch && monthMatch;
|
||||
}, archiveComparator());
|
||||
|
||||
Map<String, List<PostVo>> yearPosts = list.get()
|
||||
.collect(Collectors.groupingBy(
|
||||
post -> HaloUtils.getYearText(post.getSpec().getPublishTime())));
|
||||
List<PostArchiveVo> postArchives =
|
||||
yearPosts.entrySet().stream().map(entry -> {
|
||||
String key = entry.getKey();
|
||||
// archives by month
|
||||
Map<String, List<PostVo>> monthPosts = entry.getValue().stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
post -> HaloUtils.getMonthText(post.getSpec().getPublishTime())));
|
||||
// convert to archive year month value objects
|
||||
List<PostArchiveYearMonthVo> monthArchives = monthPosts.entrySet()
|
||||
.stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.map(monthEntry -> PostArchiveYearMonthVo.builder()
|
||||
.posts(monthEntry.getValue())
|
||||
.month(monthEntry.getKey())
|
||||
.build()
|
||||
)
|
||||
.toList();
|
||||
return PostArchiveVo.builder()
|
||||
.year(String.valueOf(key))
|
||||
.months(monthArchives)
|
||||
.build();
|
||||
}).toList();
|
||||
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(), postArchives);
|
||||
}
|
||||
|
||||
private boolean contains(List<String> c, String key) {
|
||||
|
@ -112,14 +171,15 @@ public class PostFinderImpl implements PostFinder {
|
|||
return c.contains(key);
|
||||
}
|
||||
|
||||
private ListResult<PostVo> listPost(Integer page, Integer size, Predicate<Post> postPredicate) {
|
||||
private ListResult<PostVo> listPost(Integer page, Integer size, Predicate<Post> postPredicate,
|
||||
Comparator<Post> comparator) {
|
||||
Predicate<Post> predicate = FIXED_PREDICATE
|
||||
.and(postPredicate == null ? post -> true : postPredicate);
|
||||
ListResult<Post> list = client.list(Post.class, predicate,
|
||||
defaultComparator(), pageNullSafe(page), sizeNullSafe(size))
|
||||
comparator, pageNullSafe(page), sizeNullSafe(size))
|
||||
.block();
|
||||
if (list == null) {
|
||||
return new ListResult<>(0, 0, 0, List.of());
|
||||
return new ListResult<>(List.of());
|
||||
}
|
||||
List<PostVo> postVos = list.get()
|
||||
.map(this::getPostVo)
|
||||
|
@ -168,6 +228,15 @@ public class PostFinderImpl implements PostFinder {
|
|||
.reversed();
|
||||
}
|
||||
|
||||
static Comparator<Post> archiveComparator() {
|
||||
Function<Post, Instant> publishTime =
|
||||
post -> post.getSpec().getPublishTime();
|
||||
Function<Post, String> name = post -> post.getMetadata().getName();
|
||||
return Comparator.comparing(publishTime, Comparators.nullsLow())
|
||||
.thenComparing(name)
|
||||
.reversed();
|
||||
}
|
||||
|
||||
int pageNullSafe(Integer page) {
|
||||
return ObjectUtils.defaultIfNull(page, 1);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package run.halo.app.theme.finders.vo;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Post archives by year and month.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class PostArchiveVo {
|
||||
|
||||
String year;
|
||||
|
||||
List<PostArchiveYearMonthVo> months;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package run.halo.app.theme.finders.vo;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Post archives by month.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class PostArchiveYearMonthVo {
|
||||
|
||||
String month;
|
||||
|
||||
List<PostVo> posts;
|
||||
}
|
|
@ -14,7 +14,6 @@ import org.springframework.context.ApplicationContext;
|
|||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
@ -53,22 +52,7 @@ public class PermalinkHttpGetRouter implements InitializingBean {
|
|||
* @return a handler function if matched, otherwise null
|
||||
*/
|
||||
public HandlerFunction<ServerResponse> route(ServerRequest request) {
|
||||
MultiValueMap<String, String> queryParams = request.queryParams();
|
||||
String requestPath = request.path();
|
||||
// 文章的 permalink 规则需要对 p 参数规则特殊处理
|
||||
if (requestPath.equals("/") && queryParams.containsKey("p")) {
|
||||
// post special route path
|
||||
String postSlug = queryParams.getFirst("p");
|
||||
requestPath = requestPath + "?p=" + postSlug;
|
||||
}
|
||||
// /categories/{slug}/page/{page} 和 /tags/{slug}/page/{page} 需要去掉 page 部分
|
||||
if (PageUrlUtils.isPageUrl(requestPath)) {
|
||||
int i = requestPath.lastIndexOf("/page/");
|
||||
if (i != -1) {
|
||||
requestPath = requestPath.substring(0, i);
|
||||
}
|
||||
}
|
||||
return routeTree.match(requestPath);
|
||||
return routeTree.match(request);
|
||||
}
|
||||
|
||||
public void insert(String key, HandlerFunction<ServerResponse> handlerFunction) {
|
||||
|
|
|
@ -2,11 +2,18 @@ package run.halo.app.theme.router;
|
|||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
@ -66,11 +73,11 @@ public class RadixRouterTree extends RadixTree<HandlerFunction<ServerResponse>>
|
|||
* TODO Optimize matching algorithm to improve efficiency and try your best to get results
|
||||
* through one search
|
||||
*
|
||||
* @param requestPath request path
|
||||
* @param request server request
|
||||
* @return a handler function if matched, otherwise null
|
||||
*/
|
||||
public HandlerFunction<ServerResponse> match(String requestPath) {
|
||||
String path = processRequestPath(requestPath);
|
||||
public HandlerFunction<ServerResponse> match(ServerRequest request) {
|
||||
String path = pathToFind(request);
|
||||
HandlerFunction<ServerResponse> result = find(path);
|
||||
if (result != null) {
|
||||
return result;
|
||||
|
@ -106,10 +113,70 @@ public class RadixRouterTree extends RadixTree<HandlerFunction<ServerResponse>>
|
|||
+ secondBestMatch + "}");
|
||||
}
|
||||
}
|
||||
|
||||
PathPattern.PathMatchInfo info =
|
||||
bestMatch.matchAndExtract(request.requestPath().pathWithinApplication());
|
||||
if (info != null) {
|
||||
mergeAttributes(request, info.getUriVariables(), bestMatch);
|
||||
}
|
||||
return find(bestMatch.getPatternString());
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Optimize parameter route matching query.
|
||||
* Router 仅匹配请求方法和请求的 URL 路径, 形如 /?p=post-name 是 URL query,而不是 URL 路径的一部分。
|
||||
*/
|
||||
private String pathToFind(ServerRequest request) {
|
||||
String requestPath = processRequestPath(request.path());
|
||||
MultiValueMap<String, String> queryParams = request.queryParams();
|
||||
// 文章的 permalink 规则需要对 p 参数规则特殊处理
|
||||
if (requestPath.equals("/") && queryParams.containsKey("p")) {
|
||||
// post special route path
|
||||
String postSlug = queryParams.getFirst("p");
|
||||
requestPath = requestPath + "?p=" + postSlug;
|
||||
}
|
||||
// /categories/{slug}/page/{page} 和 /tags/{slug}/page/{page} 需要去掉 page 部分
|
||||
if (PageUrlUtils.isPageUrl(requestPath)) {
|
||||
int i = requestPath.lastIndexOf("/page/");
|
||||
if (i != -1) {
|
||||
requestPath = requestPath.substring(0, i);
|
||||
}
|
||||
}
|
||||
return StringUtils.prependIfMissing(requestPath, "/");
|
||||
}
|
||||
|
||||
private static void mergeAttributes(ServerRequest request, Map<String, String> variables,
|
||||
PathPattern pattern) {
|
||||
Map<String, String> pathVariables = mergePathVariables(request.pathVariables(), variables);
|
||||
request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
|
||||
Collections.unmodifiableMap(pathVariables));
|
||||
|
||||
pattern = mergePatterns(
|
||||
(PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
|
||||
pattern);
|
||||
request.attributes().put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
|
||||
}
|
||||
|
||||
private static PathPattern mergePatterns(@Nullable PathPattern oldPattern,
|
||||
PathPattern newPattern) {
|
||||
if (oldPattern != null) {
|
||||
return oldPattern.combine(newPattern);
|
||||
} else {
|
||||
return newPattern;
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> mergePathVariables(Map<String, String> oldVariables,
|
||||
Map<String, String> newVariables) {
|
||||
|
||||
if (!newVariables.isEmpty()) {
|
||||
Map<String, String> mergedVariables = new LinkedHashMap<>(oldVariables);
|
||||
mergedVariables.putAll(newVariables);
|
||||
return mergedVariables;
|
||||
} else {
|
||||
return oldVariables;
|
||||
}
|
||||
}
|
||||
|
||||
private String processRequestPath(String requestPath) {
|
||||
String path = StringUtils.prependIfMissing(requestPath, "/");
|
||||
return UriUtils.decode(path, StandardCharsets.UTF_8);
|
||||
|
|
|
@ -14,7 +14,7 @@ import reactor.core.scheduler.Schedulers;
|
|||
import run.halo.app.infra.utils.PathUtils;
|
||||
import run.halo.app.theme.DefaultTemplateEnum;
|
||||
import run.halo.app.theme.finders.PostFinder;
|
||||
import run.halo.app.theme.finders.vo.PostVo;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveVo;
|
||||
import run.halo.app.theme.router.PageUrlUtils;
|
||||
import run.halo.app.theme.router.UrlContextListResult;
|
||||
|
||||
|
@ -33,22 +33,32 @@ public class ArchivesRouteStrategy implements ListPageRouteHandlerStrategy {
|
|||
this.postFinder = postFinder;
|
||||
}
|
||||
|
||||
private Mono<UrlContextListResult<PostVo>> postList(ServerRequest request) {
|
||||
private Mono<UrlContextListResult<PostArchiveVo>> postList(ServerRequest request) {
|
||||
String year = pathVariable(request, "year");
|
||||
String month = pathVariable(request, "month");
|
||||
String path = request.path();
|
||||
return Mono.defer(() -> Mono.just(postFinder.list(pageNum(request), 10)))
|
||||
return Mono.defer(() -> Mono.just(postFinder.archives(pageNum(request), 10, year, month)))
|
||||
.publishOn(Schedulers.boundedElastic())
|
||||
.map(list -> new UrlContextListResult.Builder<PostVo>()
|
||||
.map(list -> new UrlContextListResult.Builder<PostArchiveVo>()
|
||||
.listResult(list)
|
||||
.nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list)))
|
||||
.prevUrl(PageUrlUtils.prevPageUrl(path))
|
||||
.build());
|
||||
}
|
||||
|
||||
private String pathVariable(ServerRequest request, String name) {
|
||||
Map<String, String> pathVariables = request.pathVariables();
|
||||
if (pathVariables.containsKey(name)) {
|
||||
return pathVariables.get(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerFunction<ServerResponse> getHandler() {
|
||||
return request -> ServerResponse.ok()
|
||||
.render(DefaultTemplateEnum.ARCHIVES.getValue(),
|
||||
Map.of("posts", postList(request)));
|
||||
Map.of("archives", postList(request)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -56,6 +66,8 @@ public class ArchivesRouteStrategy implements ListPageRouteHandlerStrategy {
|
|||
return List.of(
|
||||
prefix,
|
||||
PathUtils.combinePath(prefix, "/page/{page:\\d+}"),
|
||||
PathUtils.combinePath(prefix, "/{year:\\d{4}}"),
|
||||
PathUtils.combinePath(prefix, "/{year:\\d{4}}/page/{page:\\d+}"),
|
||||
PathUtils.combinePath(prefix, "/{year:\\d{4}}/{month:\\d{2}}"),
|
||||
PathUtils.combinePath(prefix,
|
||||
"/{year:\\d{4}}/{month:\\d{2}}/page/{page:\\d+}")
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package run.halo.app.theme.finders.impl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
@ -15,13 +17,17 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.core.extension.Counter;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.theme.finders.CategoryFinder;
|
||||
import run.halo.app.theme.finders.ContributorFinder;
|
||||
import run.halo.app.theme.finders.TagFinder;
|
||||
import run.halo.app.theme.finders.vo.ContentVo;
|
||||
import run.halo.app.theme.finders.vo.PostArchiveVo;
|
||||
|
||||
/**
|
||||
* Tests for {@link PostFinderImpl}.
|
||||
|
@ -38,6 +44,9 @@ class PostFinderImplTest {
|
|||
@Mock
|
||||
private ContentService contentService;
|
||||
|
||||
@Mock
|
||||
private CounterService counterService;
|
||||
|
||||
@Mock
|
||||
private CategoryFinder categoryFinder;
|
||||
|
||||
|
@ -81,6 +90,48 @@ class PostFinderImplTest {
|
|||
assertThat(strings).isEqualTo(List.of("post-1", "post-2", "post-6"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void archives() {
|
||||
Counter counter = new Counter();
|
||||
counter.setMetadata(new Metadata());
|
||||
when(counterService.getByName(any())).thenReturn(counter);
|
||||
ListResult<Post> listResult = new ListResult<>(1, 10, 3, postsForArchives());
|
||||
when(client.list(eq(Post.class), any(), any(), anyInt(), anyInt()))
|
||||
.thenReturn(Mono.just(listResult));
|
||||
ListResult<PostArchiveVo> archives = postFinder.archives(1, 10);
|
||||
List<PostArchiveVo> items = archives.getItems();
|
||||
|
||||
assertThat(items.size()).isEqualTo(2);
|
||||
assertThat(items.get(0).getYear()).isEqualTo("2022");
|
||||
assertThat(items.get(0).getMonths().size()).isEqualTo(2);
|
||||
assertThat(items.get(0).getMonths().get(0).getMonth()).isEqualTo("10");
|
||||
assertThat(items.get(0).getMonths().get(1).getMonth()).isEqualTo("12");
|
||||
assertThat(items.get(0).getMonths().get(1).getPosts().size()).isEqualTo(1);
|
||||
assertThat(items.get(0).getMonths().get(1).getPosts().size()).isEqualTo(1);
|
||||
|
||||
assertThat(items.get(1).getYear()).isEqualTo("2021");
|
||||
assertThat(items.get(1).getMonths()).hasSize(1);
|
||||
assertThat(items.get(1).getMonths().get(0).getMonth()).isEqualTo("01");
|
||||
}
|
||||
|
||||
List<Post> postsForArchives() {
|
||||
Post post1 = post(1);
|
||||
post1.getSpec().setPublished(true);
|
||||
post1.getSpec().setPublishTime(Instant.parse("2021-01-01T00:00:00Z"));
|
||||
post1.getMetadata().setCreationTimestamp(Instant.now());
|
||||
|
||||
Post post2 = post(2);
|
||||
post2.getSpec().setPublished(true);
|
||||
post2.getSpec().setPublishTime(Instant.parse("2022-12-01T00:00:00Z"));
|
||||
post2.getMetadata().setCreationTimestamp(Instant.now());
|
||||
|
||||
Post post3 = post(3);
|
||||
post2.getSpec().setPublished(true);
|
||||
post2.getSpec().setPublishTime(Instant.parse("2022-12-03T00:00:00Z"));
|
||||
post3.getMetadata().setCreationTimestamp(Instant.now());
|
||||
return List.of(post1, post2, post3);
|
||||
}
|
||||
|
||||
List<Post> posts() {
|
||||
// 置顶的排前面按 priority 排序
|
||||
// 再根据创建时间排序
|
||||
|
|
|
@ -55,6 +55,12 @@ class IndexRouteStrategyTest extends RouterStrategyTestSuite {
|
|||
.expectStatus()
|
||||
.isOk();
|
||||
|
||||
client.get()
|
||||
.uri("/page/1")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
|
||||
client.get()
|
||||
.uri("/nothing")
|
||||
.exchange()
|
||||
|
|
Loading…
Reference in New Issue