From 160dd909cce6cec795f636bc87d593787d9056a0 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:46:12 +0800 Subject: [PATCH] feat: provides the route of the post archive page for theme-side (#2598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### 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 为主题提供文章归档页 ``` --- .../run/halo/app/core/extension/Post.java | 4 + .../extension/reconciler/PostReconciler.java | 6 ++ .../run/halo/app/infra/utils/HaloUtils.java | 14 ++++ .../halo/app/theme/finders/PostFinder.java | 7 ++ .../theme/finders/impl/PostFinderImpl.java | 81 +++++++++++++++++-- .../app/theme/finders/vo/PostArchiveVo.java | 20 +++++ .../finders/vo/PostArchiveYearMonthVo.java | 20 +++++ .../theme/router/PermalinkHttpGetRouter.java | 18 +---- .../app/theme/router/RadixRouterTree.java | 75 ++++++++++++++++- .../strategy/ArchivesRouteStrategy.java | 22 +++-- .../finders/impl/PostFinderImplTest.java | 51 ++++++++++++ .../strategy/IndexRouteStrategyTest.java | 6 ++ 12 files changed, 292 insertions(+), 32 deletions(-) create mode 100644 src/main/java/run/halo/app/theme/finders/vo/PostArchiveVo.java create mode 100644 src/main/java/run/halo/app/theme/finders/vo/PostArchiveYearMonthVo.java diff --git a/src/main/java/run/halo/app/core/extension/Post.java b/src/main/java/run/halo/app/core/extension/Post.java index 9d9b7862f..ebaed8760 100644 --- a/src/main/java/run/halo/app/core/extension/Post.java +++ b/src/main/java/run/halo/app/core/extension/Post.java @@ -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; diff --git a/src/main/java/run/halo/app/core/extension/reconciler/PostReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/PostReconciler.java index 56dad9d26..dd9325012 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/PostReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/PostReconciler.java @@ -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 { 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); diff --git a/src/main/java/run/halo/app/infra/utils/HaloUtils.java b/src/main/java/run/halo/app/infra/utils/HaloUtils.java index 819944f56..caaefbe67 100644 --- a/src/main/java/run/halo/app/infra/utils/HaloUtils.java +++ b/src/main/java/run/halo/app/infra/utils/HaloUtils.java @@ -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()); + } } diff --git a/src/main/java/run/halo/app/theme/finders/PostFinder.java b/src/main/java/run/halo/app/theme/finders/PostFinder.java index 267871f85..d7a35ecfc 100644 --- a/src/main/java/run/halo/app/theme/finders/PostFinder.java +++ b/src/main/java/run/halo/app/theme/finders/PostFinder.java @@ -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 listByTag(@Nullable Integer page, @Nullable Integer size, String tag); + + ListResult archives(Integer page, Integer size); + + ListResult archives(Integer page, Integer size, String year); + + ListResult archives(Integer page, Integer size, String year, String month); } diff --git a/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java b/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java index ff4cd13e1..e5a62cb46 100644 --- a/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java +++ b/src/main/java/run/halo/app/theme/finders/impl/PostFinderImpl.java @@ -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 list(Integer page, Integer size) { - return listPost(page, size, null); + return listPost(page, size, null, defaultComparator()); } @Override public ListResult 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 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 archives(Integer page, Integer size) { + return archives(page, size, null, null); + } + + @Override + public ListResult archives(Integer page, Integer size, String year) { + return archives(page, size, year, null); + } + + @Override + public ListResult archives(Integer page, Integer size, String year, + String month) { + ListResult list = listPost(page, size, post -> { + Map 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> yearPosts = list.get() + .collect(Collectors.groupingBy( + post -> HaloUtils.getYearText(post.getSpec().getPublishTime()))); + List postArchives = + yearPosts.entrySet().stream().map(entry -> { + String key = entry.getKey(); + // archives by month + Map> monthPosts = entry.getValue().stream() + .collect(Collectors.groupingBy( + post -> HaloUtils.getMonthText(post.getSpec().getPublishTime()))); + // convert to archive year month value objects + List 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 c, String key) { @@ -112,14 +171,15 @@ public class PostFinderImpl implements PostFinder { return c.contains(key); } - private ListResult listPost(Integer page, Integer size, Predicate postPredicate) { + private ListResult listPost(Integer page, Integer size, Predicate postPredicate, + Comparator comparator) { Predicate predicate = FIXED_PREDICATE .and(postPredicate == null ? post -> true : postPredicate); ListResult 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 postVos = list.get() .map(this::getPostVo) @@ -168,6 +228,15 @@ public class PostFinderImpl implements PostFinder { .reversed(); } + static Comparator archiveComparator() { + Function publishTime = + post -> post.getSpec().getPublishTime(); + Function name = post -> post.getMetadata().getName(); + return Comparator.comparing(publishTime, Comparators.nullsLow()) + .thenComparing(name) + .reversed(); + } + int pageNullSafe(Integer page) { return ObjectUtils.defaultIfNull(page, 1); } diff --git a/src/main/java/run/halo/app/theme/finders/vo/PostArchiveVo.java b/src/main/java/run/halo/app/theme/finders/vo/PostArchiveVo.java new file mode 100644 index 000000000..45f953efe --- /dev/null +++ b/src/main/java/run/halo/app/theme/finders/vo/PostArchiveVo.java @@ -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 months; +} diff --git a/src/main/java/run/halo/app/theme/finders/vo/PostArchiveYearMonthVo.java b/src/main/java/run/halo/app/theme/finders/vo/PostArchiveYearMonthVo.java new file mode 100644 index 000000000..fa0e2e315 --- /dev/null +++ b/src/main/java/run/halo/app/theme/finders/vo/PostArchiveYearMonthVo.java @@ -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 posts; +} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkHttpGetRouter.java b/src/main/java/run/halo/app/theme/router/PermalinkHttpGetRouter.java index 96aaab41a..e56d71b69 100644 --- a/src/main/java/run/halo/app/theme/router/PermalinkHttpGetRouter.java +++ b/src/main/java/run/halo/app/theme/router/PermalinkHttpGetRouter.java @@ -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 route(ServerRequest request) { - MultiValueMap 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 handlerFunction) { diff --git a/src/main/java/run/halo/app/theme/router/RadixRouterTree.java b/src/main/java/run/halo/app/theme/router/RadixRouterTree.java index c70dd5e1e..1a2d5f38e 100644 --- a/src/main/java/run/halo/app/theme/router/RadixRouterTree.java +++ b/src/main/java/run/halo/app/theme/router/RadixRouterTree.java @@ -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> * 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 match(String requestPath) { - String path = processRequestPath(requestPath); + public HandlerFunction match(ServerRequest request) { + String path = pathToFind(request); HandlerFunction result = find(path); if (result != null) { return result; @@ -106,10 +113,70 @@ public class RadixRouterTree extends RadixTree> + 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 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 variables, + PathPattern pattern) { + Map 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 mergePathVariables(Map oldVariables, + Map newVariables) { + + if (!newVariables.isEmpty()) { + Map 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); diff --git a/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java index 3bcbba31b..f64b8c3aa 100644 --- a/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java +++ b/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java @@ -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> postList(ServerRequest request) { + private Mono> 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() + .map(list -> new UrlContextListResult.Builder() .listResult(list) .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) .prevUrl(PageUrlUtils.prevPageUrl(path)) .build()); } + private String pathVariable(ServerRequest request, String name) { + Map pathVariables = request.pathVariables(); + if (pathVariables.containsKey(name)) { + return pathVariables.get(name); + } + return null; + } + @Override public HandlerFunction 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+}") diff --git a/src/test/java/run/halo/app/theme/finders/impl/PostFinderImplTest.java b/src/test/java/run/halo/app/theme/finders/impl/PostFinderImplTest.java index 85158ca15..4847f6f5f 100644 --- a/src/test/java/run/halo/app/theme/finders/impl/PostFinderImplTest.java +++ b/src/test/java/run/halo/app/theme/finders/impl/PostFinderImplTest.java @@ -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 listResult = new ListResult<>(1, 10, 3, postsForArchives()); + when(client.list(eq(Post.class), any(), any(), anyInt(), anyInt())) + .thenReturn(Mono.just(listResult)); + ListResult archives = postFinder.archives(1, 10); + List 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 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 posts() { // 置顶的排前面按 priority 排序 // 再根据创建时间排序 diff --git a/src/test/java/run/halo/app/theme/router/strategy/IndexRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/IndexRouteStrategyTest.java index 6111a41d7..a75f68887 100644 --- a/src/test/java/run/halo/app/theme/router/strategy/IndexRouteStrategyTest.java +++ b/src/test/java/run/halo/app/theme/router/strategy/IndexRouteStrategyTest.java @@ -55,6 +55,12 @@ class IndexRouteStrategyTest extends RouterStrategyTestSuite { .expectStatus() .isOk(); + client.get() + .uri("/page/1") + .exchange() + .expectStatus() + .isOk(); + client.get() .uri("/nothing") .exchange()