diff --git a/src/main/java/run/halo/app/content/permalinks/CategoryPermalinkPolicy.java b/src/main/java/run/halo/app/content/permalinks/CategoryPermalinkPolicy.java index bfc83f744ec..8ce95e151fd 100644 --- a/src/main/java/run/halo/app/content/permalinks/CategoryPermalinkPolicy.java +++ b/src/main/java/run/halo/app/content/permalinks/CategoryPermalinkPolicy.java @@ -3,79 +3,46 @@ import static org.springframework.web.util.UriUtils.encode; import java.nio.charset.StandardCharsets; -import org.springframework.context.ApplicationContext; +import java.util.Map; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import run.halo.app.core.extension.content.Category; -import run.halo.app.extension.GroupVersionKind; +import run.halo.app.core.extension.content.Constant; +import run.halo.app.extension.ExtensionUtil; import run.halo.app.infra.ExternalUrlSupplier; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; import run.halo.app.infra.utils.PathUtils; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.router.PermalinkIndexAddCommand; -import run.halo.app.theme.router.PermalinkIndexDeleteCommand; -import run.halo.app.theme.router.PermalinkIndexUpdateCommand; -import run.halo.app.theme.router.PermalinkPatternProvider; -import run.halo.app.theme.router.PermalinkWatch; /** * @author guqing * @since 2.0.0 */ @Component -public class CategoryPermalinkPolicy - implements PermalinkPolicy, PermalinkWatch { - private final GroupVersionKind gvk = GroupVersionKind.fromExtension(Category.class); - private final ApplicationContext applicationContext; - private final PermalinkPatternProvider permalinkPatternProvider; +@RequiredArgsConstructor +public class CategoryPermalinkPolicy implements PermalinkPolicy { + public static final String DEFAULT_PERMALINK_PREFIX = + SystemSetting.ThemeRouteRules.empty().getCategories(); private final ExternalUrlSupplier externalUrlSupplier; - - public CategoryPermalinkPolicy(ApplicationContext applicationContext, - PermalinkPatternProvider permalinkPatternProvider, - ExternalUrlSupplier externalUrlSupplier) { - this.applicationContext = applicationContext; - this.permalinkPatternProvider = permalinkPatternProvider; - this.externalUrlSupplier = externalUrlSupplier; - } + private final SystemConfigurableEnvironmentFetcher environmentFetcher; @Override public String permalink(Category category) { + Map annotations = ExtensionUtil.nullSafeAnnotations(category); + String permalinkPrefix = + annotations.getOrDefault(Constant.PERMALINK_PATTERN_ANNO, DEFAULT_PERMALINK_PREFIX); String slug = encode(category.getSpec().getSlug(), StandardCharsets.UTF_8); - String path = PathUtils.combinePath(pattern(), slug); + String path = PathUtils.combinePath(permalinkPrefix, slug); return externalUrlSupplier.get() .resolve(path) .normalize().toString(); } - @Override - public String templateName() { - return DefaultTemplateEnum.CATEGORY.getValue(); - } - - @Override public String pattern() { - return permalinkPatternProvider.getPattern(DefaultTemplateEnum.CATEGORY); - } - - @Override - public void onPermalinkAdd(Category category) { - applicationContext.publishEvent(new PermalinkIndexAddCommand(this, getLocator(category), - category.getStatusOrDefault().getPermalink())); - } - - @Override - public void onPermalinkUpdate(Category category) { - applicationContext.publishEvent(new PermalinkIndexUpdateCommand(this, getLocator(category), - category.getStatusOrDefault().getPermalink())); - } - - @Override - public void onPermalinkDelete(Category category) { - applicationContext.publishEvent( - new PermalinkIndexDeleteCommand(this, getLocator(category))); - } - - private ExtensionLocator getLocator(Category category) { - return new ExtensionLocator(gvk, category.getMetadata().getName(), - category.getSpec().getSlug()); + return environmentFetcher.fetchRouteRules() + .map(SystemSetting.ThemeRouteRules::getCategories) + .blockOptional() + .orElse(DEFAULT_PERMALINK_PREFIX); } } diff --git a/src/main/java/run/halo/app/content/permalinks/PermalinkPolicy.java b/src/main/java/run/halo/app/content/permalinks/PermalinkPolicy.java index bbc75ada48a..03ec67ae795 100644 --- a/src/main/java/run/halo/app/content/permalinks/PermalinkPolicy.java +++ b/src/main/java/run/halo/app/content/permalinks/PermalinkPolicy.java @@ -13,8 +13,4 @@ public interface PermalinkPolicy { new PropertyPlaceholderHelper("{", "}"); String permalink(T extension); - - String templateName(); - - String pattern(); } diff --git a/src/main/java/run/halo/app/content/permalinks/PostPermalinkPolicy.java b/src/main/java/run/halo/app/content/permalinks/PostPermalinkPolicy.java index c21f46ada9e..c4c9e3cdc5f 100644 --- a/src/main/java/run/halo/app/content/permalinks/PostPermalinkPolicy.java +++ b/src/main/java/run/halo/app/content/permalinks/PostPermalinkPolicy.java @@ -8,79 +8,45 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.Objects; +import java.util.Map; import java.util.Properties; -import org.springframework.context.ApplicationContext; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import run.halo.app.core.extension.content.Constant; import run.halo.app.core.extension.content.Post; -import run.halo.app.extension.GroupVersionKind; +import run.halo.app.extension.ExtensionUtil; import run.halo.app.infra.ExternalUrlSupplier; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; import run.halo.app.infra.utils.PathUtils; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.router.PermalinkIndexAddCommand; -import run.halo.app.theme.router.PermalinkIndexDeleteCommand; -import run.halo.app.theme.router.PermalinkIndexUpdateCommand; -import run.halo.app.theme.router.PermalinkPatternProvider; -import run.halo.app.theme.router.PermalinkWatch; /** * @author guqing * @since 2.0.0 */ @Component -public class PostPermalinkPolicy implements PermalinkPolicy, PermalinkWatch { - private final GroupVersionKind gvk = GroupVersionKind.fromExtension(Post.class); +@RequiredArgsConstructor +public class PostPermalinkPolicy implements PermalinkPolicy { + public static final String DEFAULT_PERMALINK_PATTERN = + SystemSetting.ThemeRouteRules.empty().getPost(); private static final NumberFormat NUMBER_FORMAT = new DecimalFormat("00"); - private final PermalinkPatternProvider permalinkPatternProvider; - private final ApplicationContext applicationContext; + private final SystemConfigurableEnvironmentFetcher environmentFetcher; private final ExternalUrlSupplier externalUrlSupplier; - public PostPermalinkPolicy(PermalinkPatternProvider permalinkPatternProvider, - ApplicationContext applicationContext, ExternalUrlSupplier externalUrlSupplier) { - this.permalinkPatternProvider = permalinkPatternProvider; - this.applicationContext = applicationContext; - this.externalUrlSupplier = externalUrlSupplier; - } - @Override public String permalink(Post post) { - return createPermalink(post, pattern()); - } - - @Override - public String templateName() { - return DefaultTemplateEnum.POST.getValue(); + Map annotations = ExtensionUtil.nullSafeAnnotations(post); + String permalinkPattern = + annotations.getOrDefault(Constant.PERMALINK_PATTERN_ANNO, DEFAULT_PERMALINK_PATTERN); + return createPermalink(post, permalinkPattern); } - @Override public String pattern() { - return permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST); - } - - @Override - public void onPermalinkAdd(Post post) { - if (!post.isPublished() || Objects.equals(true, post.getSpec().getDeleted())) { - return; - } - // publish when post is published and not deleted - applicationContext.publishEvent(new PermalinkIndexAddCommand(this, getLocator(post), - post.getStatusOrDefault().getPermalink())); - } - - @Override - public void onPermalinkUpdate(Post post) { - applicationContext.publishEvent(new PermalinkIndexUpdateCommand(this, getLocator(post), - post.getStatusOrDefault().getPermalink())); - } - - @Override - public void onPermalinkDelete(Post post) { - applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(post))); - } - - private ExtensionLocator getLocator(Post post) { - return new ExtensionLocator(gvk, post.getMetadata().getName(), post.getSpec().getSlug()); + return environmentFetcher.fetchRouteRules() + .map(SystemSetting.ThemeRouteRules::getPost) + .blockOptional() + .orElse(DEFAULT_PERMALINK_PATTERN); } private String createPermalink(Post post, String pattern) { diff --git a/src/main/java/run/halo/app/content/permalinks/TagPermalinkPolicy.java b/src/main/java/run/halo/app/content/permalinks/TagPermalinkPolicy.java index 8000e9862b8..bb86ab5c5fd 100644 --- a/src/main/java/run/halo/app/content/permalinks/TagPermalinkPolicy.java +++ b/src/main/java/run/halo/app/content/permalinks/TagPermalinkPolicy.java @@ -1,76 +1,47 @@ package run.halo.app.content.permalinks; import java.nio.charset.StandardCharsets; -import org.springframework.context.ApplicationContext; +import java.util.Map; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.web.util.UriUtils; +import run.halo.app.core.extension.content.Constant; import run.halo.app.core.extension.content.Tag; -import run.halo.app.extension.GroupVersionKind; +import run.halo.app.extension.ExtensionUtil; import run.halo.app.infra.ExternalUrlSupplier; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; import run.halo.app.infra.utils.PathUtils; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.router.PermalinkIndexAddCommand; -import run.halo.app.theme.router.PermalinkIndexDeleteCommand; -import run.halo.app.theme.router.PermalinkIndexUpdateCommand; -import run.halo.app.theme.router.PermalinkPatternProvider; -import run.halo.app.theme.router.PermalinkWatch; /** * @author guqing * @since 2.0.0 */ @Component -public class TagPermalinkPolicy implements PermalinkPolicy, PermalinkWatch { - private final GroupVersionKind gvk = GroupVersionKind.fromExtension(Tag.class); - private final PermalinkPatternProvider permalinkPatternProvider; - private final ApplicationContext applicationContext; - +@RequiredArgsConstructor +public class TagPermalinkPolicy implements PermalinkPolicy { + public static final String DEFAULT_PERMALINK_PREFIX = + SystemSetting.ThemeRouteRules.empty().getTags(); private final ExternalUrlSupplier externalUrlSupplier; - - public TagPermalinkPolicy(PermalinkPatternProvider permalinkPatternProvider, - ApplicationContext applicationContext, ExternalUrlSupplier externalUrlSupplier) { - this.permalinkPatternProvider = permalinkPatternProvider; - this.applicationContext = applicationContext; - this.externalUrlSupplier = externalUrlSupplier; - } + private final SystemConfigurableEnvironmentFetcher environmentFetcher; @Override public String permalink(Tag tag) { + Map annotations = ExtensionUtil.nullSafeAnnotations(tag); + String permalinkPrefix = + annotations.getOrDefault(Constant.PERMALINK_PATTERN_ANNO, DEFAULT_PERMALINK_PREFIX); + String slug = UriUtils.encode(tag.getSpec().getSlug(), StandardCharsets.UTF_8); - String path = PathUtils.combinePath(pattern(), slug); + String path = PathUtils.combinePath(permalinkPrefix, slug); return externalUrlSupplier.get() .resolve(path) .normalize().toString(); } - @Override - public String templateName() { - return DefaultTemplateEnum.TAG.getValue(); - } - - @Override public String pattern() { - return permalinkPatternProvider.getPattern(DefaultTemplateEnum.TAG); - } - - @Override - public void onPermalinkAdd(Tag tag) { - applicationContext.publishEvent(new PermalinkIndexAddCommand(this, getLocator(tag), - tag.getStatusOrDefault().getPermalink())); - } - - @Override - public void onPermalinkUpdate(Tag tag) { - applicationContext.publishEvent(new PermalinkIndexUpdateCommand(this, getLocator(tag), - tag.getStatusOrDefault().getPermalink())); - } - - @Override - public void onPermalinkDelete(Tag tag) { - applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(tag))); - } - - private ExtensionLocator getLocator(Tag tag) { - return new ExtensionLocator(gvk, tag.getMetadata().getName(), tag.getSpec().getSlug()); + return environmentFetcher.fetchRouteRules() + .map(SystemSetting.ThemeRouteRules::getTags) + .blockOptional() + .orElse(DEFAULT_PERMALINK_PREFIX); } } diff --git a/src/main/java/run/halo/app/core/extension/content/Constant.java b/src/main/java/run/halo/app/core/extension/content/Constant.java index d4e28d92277..93b4ba1c601 100644 --- a/src/main/java/run/halo/app/core/extension/content/Constant.java +++ b/src/main/java/run/halo/app/core/extension/content/Constant.java @@ -7,4 +7,5 @@ public enum Constant { public static final String VERSION = "v1alpha1"; public static final String LAST_READ_TIME_ANNO = "content.halo.run/last-read-time"; + public static final String PERMALINK_PATTERN_ANNO = "content.halo.run/permalink-pattern"; } diff --git a/src/main/java/run/halo/app/core/extension/reconciler/CategoryReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/CategoryReconciler.java index b03a2fcdfbc..67bdf64fde5 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/CategoryReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/CategoryReconciler.java @@ -11,12 +11,16 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import run.halo.app.content.permalinks.CategoryPermalinkPolicy; import run.halo.app.core.extension.content.Category; +import run.halo.app.core.extension.content.Constant; import run.halo.app.core.extension.content.Post; import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.ExtensionUtil; import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.ControllerBuilder; import run.halo.app.extension.controller.Reconciler; @@ -29,17 +33,12 @@ * @since 2.0.0 */ @Component +@AllArgsConstructor public class CategoryReconciler implements Reconciler { private static final String FINALIZER_NAME = "category-protection"; private final ExtensionClient client; private final CategoryPermalinkPolicy categoryPermalinkPolicy; - public CategoryReconciler(ExtensionClient client, - CategoryPermalinkPolicy categoryPermalinkPolicy) { - this.client = client; - this.categoryPermalinkPolicy = categoryPermalinkPolicy; - } - @Override public Result reconcile(Request request) { return client.fetch(Category.class, request.name()) @@ -50,6 +49,8 @@ public Result reconcile(Request request) { } addFinalizerIfNecessary(category); + reconcileMetadata(request.name()); + reconcileStatusPermalink(request.name()); reconcileStatusPosts(request.name()); @@ -65,6 +66,20 @@ public Controller setupWith(ControllerBuilder builder) { .build(); } + void reconcileMetadata(String name) { + client.fetch(Category.class, name).ifPresent(category -> { + Map annotations = ExtensionUtil.nullSafeAnnotations(category); + String oldPermalinkPattern = annotations.get(Constant.PERMALINK_PATTERN_ANNO); + + String newPattern = categoryPermalinkPolicy.pattern(); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, newPattern); + + if (!StringUtils.equals(oldPermalinkPattern, newPattern)) { + client.update(category); + } + }); + } + private void addFinalizerIfNecessary(Category oldCategory) { Set finalizers = oldCategory.getMetadata().getFinalizers(); if (finalizers != null && finalizers.contains(FINALIZER_NAME)) { @@ -82,14 +97,8 @@ private void addFinalizerIfNecessary(Category oldCategory) { }); } - private void cleanUpResources(Category category) { - // remove permalink from permalink indexer - categoryPermalinkPolicy.onPermalinkDelete(category); - } - private void cleanUpResourcesAndRemoveFinalizer(String categoryName) { client.fetch(Category.class, categoryName).ifPresent(category -> { - cleanUpResources(category); if (category.getMetadata().getFinalizers() != null) { category.getMetadata().getFinalizers().remove(FINALIZER_NAME); } @@ -101,11 +110,8 @@ private void reconcileStatusPermalink(String name) { client.fetch(Category.class, name) .ifPresent(category -> { Category oldCategory = JsonUtils.deepCopy(category); - categoryPermalinkPolicy.onPermalinkDelete(oldCategory); - category.getStatusOrDefault() .setPermalink(categoryPermalinkPolicy.permalink(category)); - categoryPermalinkPolicy.onPermalinkAdd(category); if (!oldCategory.equals(category)) { client.update(category); 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 1d919a9cbc0..4985e07981c 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 @@ -16,6 +16,7 @@ import run.halo.app.content.PostService; import run.halo.app.content.permalinks.PostPermalinkPolicy; import run.halo.app.core.extension.content.Comment; +import run.halo.app.core.extension.content.Constant; import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.Snapshot; import run.halo.app.event.post.PostPublishedEvent; @@ -242,6 +243,11 @@ private void reconcileMetadata(String name) { if (!labels.containsKey(Post.PUBLISHED_LABEL)) { labels.put(Post.PUBLISHED_LABEL, Boolean.FALSE.toString()); } + + Map annotations = ExtensionUtil.nullSafeAnnotations(post); + String newPattern = postPermalinkPolicy.pattern(); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, newPattern); + if (!oldPost.equals(post)) { client.update(post); } @@ -251,11 +257,9 @@ private void reconcileMetadata(String name) { private void reconcileStatus(String name) { client.fetch(Post.class, name).ifPresent(post -> { final Post oldPost = JsonUtils.deepCopy(post); - postPermalinkPolicy.onPermalinkDelete(oldPost); post.getStatusOrDefault() .setPermalink(postPermalinkPolicy.permalink(post)); - postPermalinkPolicy.onPermalinkAdd(post); Post.PostStatus status = post.getStatusOrDefault(); if (status.getPhase() == null) { @@ -345,9 +349,6 @@ private void cleanUpResourcesAndRemoveFinalizer(String postName) { } private void cleanUpResources(Post post) { - // remove permalink from permalink indexer - postPermalinkPolicy.onPermalinkDelete(post); - // clean up snapshots final Ref ref = Ref.of(post); client.list(Snapshot.class, diff --git a/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java index 981fd107e09..73f92212732 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java @@ -14,11 +14,9 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import run.halo.app.content.SinglePageService; -import run.halo.app.content.permalinks.ExtensionLocator; import run.halo.app.core.extension.content.Comment; import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.SinglePage; @@ -26,7 +24,6 @@ import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.ExtensionOperator; import run.halo.app.extension.ExtensionUtil; -import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.Ref; import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.ControllerBuilder; @@ -38,8 +35,6 @@ import run.halo.app.infra.utils.JsonUtils; import run.halo.app.metrics.CounterService; import run.halo.app.metrics.MeterUtils; -import run.halo.app.theme.router.PermalinkIndexAddCommand; -import run.halo.app.theme.router.PermalinkIndexDeleteCommand; /** *

Reconciler for {@link SinglePage}.

@@ -58,10 +53,8 @@ @Component public class SinglePageReconciler implements Reconciler { private static final String FINALIZER_NAME = "single-page-protection"; - private static final GroupVersionKind GVK = GroupVersionKind.fromExtension(SinglePage.class); private final ExtensionClient client; private final SinglePageService singlePageService; - private final ApplicationContext applicationContext; private final CounterService counterService; private final ExternalUrlSupplier externalUrlSupplier; @@ -237,9 +230,6 @@ private void addFinalizerIfNecessary(SinglePage oldSinglePage) { } private void cleanUpResources(SinglePage singlePage) { - // remove permalink from permalink indexer - permalinkOnDelete(singlePage); - // clean up snapshot Ref ref = Ref.of(singlePage); client.list(Snapshot.class, @@ -291,36 +281,18 @@ private void reconcileMetadata(String name) { }); } - private void permalinkOnDelete(SinglePage singlePage) { - ExtensionLocator locator = new ExtensionLocator(GVK, singlePage.getMetadata().getName(), - singlePage.getSpec().getSlug()); - applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, locator)); - } - String createPermalink(SinglePage page) { var permalink = encodePath(page.getSpec().getSlug(), UTF_8); permalink = StringUtils.prependIfMissing(permalink, "/"); return externalUrlSupplier.get().resolve(permalink).normalize().toString(); } - private void permalinkOnAdd(SinglePage singlePage) { - if (!singlePage.isPublished() || Objects.equals(true, singlePage.getSpec().getDeleted())) { - return; - } - ExtensionLocator locator = new ExtensionLocator(GVK, singlePage.getMetadata().getName(), - singlePage.getSpec().getSlug()); - applicationContext.publishEvent(new PermalinkIndexAddCommand(this, locator, - singlePage.getStatusOrDefault().getPermalink())); - } - private void reconcileStatus(String name) { client.fetch(SinglePage.class, name).ifPresent(singlePage -> { final SinglePage oldPage = JsonUtils.deepCopy(singlePage); - permalinkOnDelete(oldPage); singlePage.getStatusOrDefault() .setPermalink(createPermalink(singlePage)); - permalinkOnAdd(singlePage); SinglePage.SinglePageSpec spec = singlePage.getSpec(); SinglePage.SinglePageStatus status = singlePage.getStatusOrDefault(); diff --git a/src/main/java/run/halo/app/core/extension/reconciler/TagReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/TagReconciler.java index 19b1e9b7b37..2f5d6b6950c 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/TagReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/TagReconciler.java @@ -3,13 +3,17 @@ import java.time.Duration; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import run.halo.app.content.permalinks.TagPermalinkPolicy; +import run.halo.app.core.extension.content.Constant; import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.Tag; import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.ExtensionUtil; import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.ControllerBuilder; import run.halo.app.extension.controller.Reconciler; @@ -42,6 +46,8 @@ public Result reconcile(Request request) { } addFinalizerIfNecessary(tag); + reconcileMetadata(request.name()); + this.reconcileStatusPermalink(request.name()); reconcileStatusPosts(request.name()); @@ -57,9 +63,18 @@ public Controller setupWith(ControllerBuilder builder) { .build(); } - private void cleanUpResources(Tag tag) { - // remove permalink from permalink indexer - tagPermalinkPolicy.onPermalinkDelete(tag); + void reconcileMetadata(String name) { + client.fetch(Tag.class, name).ifPresent(tag -> { + Map annotations = ExtensionUtil.nullSafeAnnotations(tag); + String oldPermalinkPattern = annotations.get(Constant.PERMALINK_PATTERN_ANNO); + + String newPattern = tagPermalinkPolicy.pattern(); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, newPattern); + + if (!StringUtils.equals(oldPermalinkPattern, newPattern)) { + client.update(tag); + } + }); } private void addFinalizerIfNecessary(Tag oldTag) { @@ -81,7 +96,6 @@ private void addFinalizerIfNecessary(Tag oldTag) { private void cleanUpResourcesAndRemoveFinalizer(String tagName) { client.fetch(Tag.class, tagName).ifPresent(tag -> { - cleanUpResources(tag); if (tag.getMetadata().getFinalizers() != null) { tag.getMetadata().getFinalizers().remove(FINALIZER_NAME); } @@ -93,11 +107,8 @@ private void reconcileStatusPermalink(String tagName) { client.fetch(Tag.class, tagName) .ifPresent(tag -> { Tag oldTag = JsonUtils.deepCopy(tag); - tagPermalinkPolicy.onPermalinkDelete(oldTag); - tag.getStatusOrDefault() .setPermalink(tagPermalinkPolicy.permalink(tag)); - tagPermalinkPolicy.onPermalinkAdd(tag); if (!oldTag.equals(tag)) { client.update(tag); diff --git a/src/main/java/run/halo/app/core/extension/reconciler/UserReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/UserReconciler.java index bdcbf0a7449..f54ad2ae882 100644 --- a/src/main/java/run/halo/app/core/extension/reconciler/UserReconciler.java +++ b/src/main/java/run/halo/app/core/extension/reconciler/UserReconciler.java @@ -7,10 +7,8 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; -import run.halo.app.content.permalinks.ExtensionLocator; import run.halo.app.core.extension.User; import run.halo.app.extension.ExtensionClient; -import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.ControllerBuilder; import run.halo.app.extension.controller.Reconciler; @@ -18,8 +16,6 @@ import run.halo.app.infra.AnonymousUserConst; import run.halo.app.infra.ExternalUrlSupplier; import run.halo.app.infra.utils.PathUtils; -import run.halo.app.theme.router.PermalinkIndexDeleteCommand; -import run.halo.app.theme.router.PermalinkIndexUpdateCommand; @Slf4j @Component @@ -58,21 +54,12 @@ private void updatePermalink(String name) { status.setPermalink(getUserPermalink(user)); - ExtensionLocator extensionLocator = getExtensionLocator(name); - eventPublisher.publishEvent( - new PermalinkIndexUpdateCommand(this, extensionLocator, status.getPermalink())); - if (!StringUtils.equals(oldPermalink, status.getPermalink())) { client.update(user); } }); } - private static ExtensionLocator getExtensionLocator(String name) { - return new ExtensionLocator(GroupVersionKind.fromExtension(User.class), name, - name); - } - private String getUserPermalink(User user) { return externalUrlSupplier.get() .resolve(PathUtils.combinePath("authors", user.getMetadata().getName())) @@ -98,9 +85,6 @@ private void addFinalizerIfNecessary(User oldUser) { private void cleanUpResourcesAndRemoveFinalizer(String userName) { client.fetch(User.class, userName).ifPresent(user -> { - eventPublisher.publishEvent( - new PermalinkIndexDeleteCommand(this, getExtensionLocator(userName))); - if (user.getMetadata().getFinalizers() != null) { user.getMetadata().getFinalizers().remove(FINALIZER_NAME); } diff --git a/src/main/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcher.java b/src/main/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcher.java index 21afdd1ab03..2f3f87ea998 100644 --- a/src/main/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcher.java +++ b/src/main/java/run/halo/app/infra/SystemConfigurableEnvironmentFetcher.java @@ -59,6 +59,10 @@ public Mono fetchPost() { .switchIfEmpty(Mono.just(new SystemSetting.Post())); } + public Mono fetchRouteRules() { + return fetch(SystemSetting.ThemeRouteRules.GROUP, SystemSetting.ThemeRouteRules.class); + } + @NonNull private Mono> getValuesInternal() { return getConfigMap() diff --git a/src/main/java/run/halo/app/infra/SystemSetting.java b/src/main/java/run/halo/app/infra/SystemSetting.java index d2e186dd29a..a8f25805335 100644 --- a/src/main/java/run/halo/app/infra/SystemSetting.java +++ b/src/main/java/run/halo/app/infra/SystemSetting.java @@ -32,6 +32,15 @@ public static class ThemeRouteRules { private String archives; private String post; private String tags; + + public static ThemeRouteRules empty() { + ThemeRouteRules rules = new ThemeRouteRules(); + rules.setPost("/archives/{slug}"); + rules.setArchives("/archives"); + rules.setTags("/tags"); + rules.setCategories("/categories"); + return rules; + } } @Data diff --git a/src/main/java/run/halo/app/infra/exception/NotFoundException.java b/src/main/java/run/halo/app/infra/exception/NotFoundException.java index b7ff8c5ebf0..f15716617db 100644 --- a/src/main/java/run/halo/app/infra/exception/NotFoundException.java +++ b/src/main/java/run/halo/app/infra/exception/NotFoundException.java @@ -1,21 +1,27 @@ package run.halo.app.infra.exception; +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; +import org.springframework.web.server.ResponseStatusException; + /** * Not found exception. * * @author guqing * @since 2.0.0 */ -public class NotFoundException extends RuntimeException { - public NotFoundException(String message) { - super(message); +public class NotFoundException extends ResponseStatusException { + + public NotFoundException(@Nullable String reason) { + this(reason, null); } - public NotFoundException(String message, Throwable cause) { - super(message, cause); + public NotFoundException(@Nullable String reason, + @Nullable Throwable cause) { + super(HttpStatus.NOT_FOUND, reason, cause); } - public NotFoundException(Throwable cause) { - super(cause); + public NotFoundException(@Nullable Throwable cause) { + this(cause == null ? "" : cause.getMessage(), cause); } } diff --git a/src/main/java/run/halo/app/theme/dialect/ContentTemplateHeadProcessor.java b/src/main/java/run/halo/app/theme/dialect/ContentTemplateHeadProcessor.java index 294a80f4244..02af638418a 100644 --- a/src/main/java/run/halo/app/theme/dialect/ContentTemplateHeadProcessor.java +++ b/src/main/java/run/halo/app/theme/dialect/ContentTemplateHeadProcessor.java @@ -13,7 +13,7 @@ import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.SinglePageFinder; -import run.halo.app.theme.router.strategy.ModelConst; +import run.halo.app.theme.router.factories.ModelConst; /** *

The head html snippet injection processor for content template such as post diff --git a/src/main/java/run/halo/app/theme/dialect/TemplateGlobalHeadProcessor.java b/src/main/java/run/halo/app/theme/dialect/TemplateGlobalHeadProcessor.java index 3162ce43a1a..39bb457f8de 100644 --- a/src/main/java/run/halo/app/theme/dialect/TemplateGlobalHeadProcessor.java +++ b/src/main/java/run/halo/app/theme/dialect/TemplateGlobalHeadProcessor.java @@ -10,7 +10,7 @@ import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.infra.SystemSetting; import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.router.strategy.ModelConst; +import run.halo.app.theme.router.factories.ModelConst; /** *

Global custom head snippet injection for theme global setting.

diff --git a/src/main/java/run/halo/app/theme/router/ExtensionPermalinkPatternUpdater.java b/src/main/java/run/halo/app/theme/router/ExtensionPermalinkPatternUpdater.java new file mode 100644 index 00000000000..06ba2774338 --- /dev/null +++ b/src/main/java/run/halo/app/theme/router/ExtensionPermalinkPatternUpdater.java @@ -0,0 +1,82 @@ +package run.halo.app.theme.router; + +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationListener; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import run.halo.app.core.extension.content.Category; +import run.halo.app.core.extension.content.Constant; +import run.halo.app.core.extension.content.Post; +import run.halo.app.core.extension.content.Tag; +import run.halo.app.extension.AbstractExtension; +import run.halo.app.extension.Extension; +import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.ExtensionUtil; +import run.halo.app.extension.MetadataOperator; +import run.halo.app.theme.DefaultTemplateEnum; + +/** + * {@link ExtensionPermalinkPatternUpdater} to update the value of key + * {@link Constant#PERMALINK_PATTERN_ANNO} in {@link MetadataOperator#getAnnotations()} + * of {@link Extension} when the pattern changed. + * + * @author guqing + * @see Post + * @see Category + * @see Tag + * @since 2.0.0 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ExtensionPermalinkPatternUpdater + implements ApplicationListener { + private final ExtensionClient client; + + @Override + public void onApplicationEvent(@NonNull PermalinkRuleChangedEvent event) { + DefaultTemplateEnum template = event.getTemplate(); + log.debug("Refresh permalink for template [{}]", template.getValue()); + String pattern = event.getRule(); + switch (template) { + case POST -> updatePostPermalink(pattern); + case CATEGORY -> updateCategoryPermalink(pattern); + case TAG -> updateTagPermalink(pattern); + default -> { + } + } + } + + private void updatePostPermalink(String pattern) { + log.debug("Update post permalink by new policy [{}]", pattern); + client.list(Post.class, null, null) + .forEach(post -> updateIfPermalinkPatternChanged(post, pattern)); + } + + private void updateIfPermalinkPatternChanged(AbstractExtension extension, String pattern) { + Map annotations = ExtensionUtil.nullSafeAnnotations(extension); + String oldPattern = annotations.get(Constant.PERMALINK_PATTERN_ANNO); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, pattern); + + if (StringUtils.equals(oldPattern, pattern) && StringUtils.isNotBlank(oldPattern)) { + return; + } + // update permalink pattern annotation + client.update(extension); + } + + private void updateCategoryPermalink(String pattern) { + log.debug("Update category and categories permalink by new policy [{}]", pattern); + client.list(Category.class, null, null) + .forEach(category -> updateIfPermalinkPatternChanged(category, pattern)); + } + + private void updateTagPermalink(String pattern) { + log.debug("Update tag and tags permalink by new policy [{}]", pattern); + client.list(Tag.class, null, null) + .forEach(tag -> updateIfPermalinkPatternChanged(tag, pattern)); + } +} diff --git a/src/main/java/run/halo/app/theme/router/GvkName.java b/src/main/java/run/halo/app/theme/router/GvkName.java deleted file mode 100644 index b67973c7dee..00000000000 --- a/src/main/java/run/halo/app/theme/router/GvkName.java +++ /dev/null @@ -1,12 +0,0 @@ -package run.halo.app.theme.router; - -import run.halo.app.extension.GroupVersionKind; - -/** - * A record class for holding gvk and name. - * - * @author guqing - * @since 2.0.0 - */ -public record GvkName(GroupVersionKind gvk, String name) { -} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkHttpGetRouter.java b/src/main/java/run/halo/app/theme/router/PermalinkHttpGetRouter.java deleted file mode 100644 index e56d71b69bc..00000000000 --- a/src/main/java/run/halo/app/theme/router/PermalinkHttpGetRouter.java +++ /dev/null @@ -1,212 +0,0 @@ -package run.halo.app.theme.router; - -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; -import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationContext; -import org.springframework.context.event.EventListener; -import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import org.springframework.web.util.UriUtils; -import run.halo.app.core.extension.reconciler.SystemSettingReconciler; -import run.halo.app.extension.GroupVersionKind; -import run.halo.app.extension.ReactiveExtensionClient; -import run.halo.app.infra.ExternalUrlSupplier; -import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -import run.halo.app.infra.SystemSetting; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.router.strategy.DetailsPageRouteHandlerStrategy; -import run.halo.app.theme.router.strategy.IndexRouteStrategy; -import run.halo.app.theme.router.strategy.ListPageRouteHandlerStrategy; - -/** - * Permalink router for http get method. - * - * @author guqing - * @since 2.0.0 - */ -@Component -@AllArgsConstructor -public class PermalinkHttpGetRouter implements InitializingBean { - private final ReentrantLock lock = new ReentrantLock(); - private final RadixRouterTree routeTree = new RadixRouterTree(); - private final SystemConfigurableEnvironmentFetcher environmentFetcher; - private final ReactiveExtensionClient client; - private final ApplicationContext applicationContext; - private final ExternalUrlSupplier externalUrlSupplier; - - /** - * Match permalink according to {@link ServerRequest}. - * - * @param request http request - * @return a handler function if matched, otherwise null - */ - public HandlerFunction route(ServerRequest request) { - return routeTree.match(request); - } - - public void insert(String key, HandlerFunction handlerFunction) { - routeTree.insert(key, handlerFunction); - } - - /** - * Watch permalink changed event to refresh route tree. - * - * @param event permalink changed event - */ - @EventListener(PermalinkIndexChangedEvent.class) - public void onPermalinkChanged(PermalinkIndexChangedEvent event) { - String oldPath = getPath(event.getOldPermalink()); - String path = getPath(event.getPermalink()); - GvkName gvkName = event.getGvkName(); - lock.lock(); - try { - if (oldPath == null && path != null) { - onPermalinkAdded(gvkName, path); - return; - } - - if (oldPath != null) { - if (path == null) { - onPermalinkDeleted(oldPath); - } else { - onPermalinkUpdated(gvkName, oldPath, path); - } - } - } finally { - lock.unlock(); - } - } - - /** - * Watch permalink rule changed event to refresh route tree for list style templates. - * - * @param event permalink changed event - */ - @EventListener(PermalinkRuleChangedEvent.class) - public void onPermalinkRuleChanged(PermalinkRuleChangedEvent event) { - final String rule = event.getRule(); - final String oldRule = event.getOldRule(); - lock.lock(); - try { - if (StringUtils.isNotBlank(oldRule)) { - routeTree.delete(oldRule); - } - registerByTemplate(event.getTemplate(), rule); - } finally { - lock.unlock(); - } - } - - /** - * delete theme route old rules to trigger register. - */ - @EventListener(ApplicationReadyEvent.class) - public void onApplicationReady() { - environmentFetcher.getConfigMap().flatMap(configMap -> { - Map annotations = configMap.getMetadata().getAnnotations(); - if (annotations != null) { - annotations.remove(SystemSettingReconciler.OLD_THEME_ROUTE_RULES); - } - return client.update(configMap); - }).block(); - } - - private void registerByTemplate(DefaultTemplateEnum template, String rule) { - ListPageRouteHandlerStrategy routeStrategy = getRouteStrategy(template); - if (routeStrategy == null) { - return; - } - List routerPaths = routeStrategy.getRouterPaths(rule); - routeTreeBatchOperation(routerPaths, routeTree::delete); - if (StringUtils.isNotBlank(rule)) { - routeTreeBatchOperation(routeStrategy.getRouterPaths(rule), - path -> routeTree.insert(path, routeStrategy.getHandler())); - } - } - - void init() { - // Index route need to be added first - IndexRouteStrategy indexRouteStrategy = - applicationContext.getBean(IndexRouteStrategy.class); - List routerPaths = indexRouteStrategy.getRouterPaths("/"); - routeTreeBatchOperation(routerPaths, - path -> routeTree.insert(path, indexRouteStrategy.getHandler())); - } - - private void routeTreeBatchOperation(List paths, - Consumer templateFunction) { - if (paths == null) { - return; - } - paths.forEach(templateFunction); - } - - private void onPermalinkAdded(GvkName gvkName, String path) { - routeTree.insert(path, getRouteHandler(gvkName)); - } - - private void onPermalinkUpdated(GvkName gvkName, String oldPath, String path) { - routeTree.delete(oldPath); - routeTree.insert(path, getRouteHandler(gvkName)); - - } - - private void onPermalinkDeleted(String path) { - routeTree.delete(path); - } - - private String getPath(@Nullable String permalink) { - if (permalink == null) { - return null; - } - String decode = UriUtils.decode(permalink, StandardCharsets.UTF_8); - URI externalUrl = externalUrlSupplier.get(); - if (externalUrl != null) { - String externalAsciiUrl = externalUrl.toASCIIString(); - return StringUtils.prependIfMissing( - StringUtils.removeStart(decode, externalAsciiUrl), "/"); - } - return decode; - } - - private HandlerFunction getRouteHandler(GvkName gvkName) { - GroupVersionKind gvk = gvkName.gvk(); - return applicationContext.getBeansOfType(DetailsPageRouteHandlerStrategy.class) - .values() - .stream() - .filter(strategy -> strategy.supports(gvk)) - .findFirst() - .map(strategy -> strategy.getHandler(getThemeRouteRules(), gvkName.name())) - .orElse(null); - } - - private ListPageRouteHandlerStrategy getRouteStrategy(DefaultTemplateEnum template) { - return applicationContext.getBeansOfType(ListPageRouteHandlerStrategy.class) - .values() - .stream() - .filter(strategy -> strategy.supports(template)) - .findFirst() - .orElse(null); - } - - public SystemSetting.ThemeRouteRules getThemeRouteRules() { - return environmentFetcher.fetch(SystemSetting.ThemeRouteRules.GROUP, - SystemSetting.ThemeRouteRules.class).block(); - } - - @Override - public void afterPropertiesSet() throws Exception { - init(); - } -} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkIndexAddCommand.java b/src/main/java/run/halo/app/theme/router/PermalinkIndexAddCommand.java deleted file mode 100644 index 3ee8a2ea142..00000000000 --- a/src/main/java/run/halo/app/theme/router/PermalinkIndexAddCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package run.halo.app.theme.router; - -import org.springframework.context.ApplicationEvent; -import run.halo.app.content.permalinks.ExtensionLocator; - -/** - *

Send a command to add a piece of data from {@link PermalinkIndexer}.

- * - * @author guqing - * @see PermalinkIndexer - * @since 2.0.0 - */ -public class PermalinkIndexAddCommand extends ApplicationEvent { - private final ExtensionLocator locator; - private final String permalink; - - public PermalinkIndexAddCommand(Object source, ExtensionLocator locator, String permalink) { - super(source); - this.locator = locator; - this.permalink = permalink; - } - - public ExtensionLocator getLocator() { - return locator; - } - - public String getPermalink() { - return permalink; - } -} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkIndexChangedEvent.java b/src/main/java/run/halo/app/theme/router/PermalinkIndexChangedEvent.java deleted file mode 100644 index e99ecbf0640..00000000000 --- a/src/main/java/run/halo/app/theme/router/PermalinkIndexChangedEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package run.halo.app.theme.router; - -import lombok.Getter; -import org.springframework.context.ApplicationEvent; -import org.springframework.util.Assert; - -/** - * Permalink index changed event. - * - * @author guqing - * @since 2.0.0 - */ -@Getter -public class PermalinkIndexChangedEvent extends ApplicationEvent { - private final String oldPermalink; - private final String permalink; - - private final GvkName gvkName; - - public PermalinkIndexChangedEvent(Object source, - GvkName gvkName, String oldPermalink, String permalink) { - super(source); - Assert.notNull(gvkName, "The gvkName must not be null."); - this.oldPermalink = oldPermalink; - this.permalink = permalink; - this.gvkName = gvkName; - } -} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkIndexDeleteCommand.java b/src/main/java/run/halo/app/theme/router/PermalinkIndexDeleteCommand.java deleted file mode 100644 index d5bd880f32b..00000000000 --- a/src/main/java/run/halo/app/theme/router/PermalinkIndexDeleteCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package run.halo.app.theme.router; - -import org.springframework.context.ApplicationEvent; -import run.halo.app.content.permalinks.ExtensionLocator; - -/** - *

Send a command to delete a piece of data from {@link PermalinkIndexer}.

- * - * @author guqing - * @see PermalinkIndexer - * @since 2.0.0 - */ -public class PermalinkIndexDeleteCommand extends ApplicationEvent { - private final ExtensionLocator locator; - - public PermalinkIndexDeleteCommand(Object source, ExtensionLocator locator) { - super(source); - this.locator = locator; - } - - public ExtensionLocator getLocator() { - return locator; - } -} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkIndexUpdateCommand.java b/src/main/java/run/halo/app/theme/router/PermalinkIndexUpdateCommand.java deleted file mode 100644 index 3ba417355ac..00000000000 --- a/src/main/java/run/halo/app/theme/router/PermalinkIndexUpdateCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package run.halo.app.theme.router; - -import org.springframework.context.ApplicationEvent; -import run.halo.app.content.permalinks.ExtensionLocator; - -/** - *

Send a command to update a piece of data from {@link PermalinkIndexer}.

- * - * @author guqing - * @see PermalinkIndexer - * @since 2.0.0 - */ -public class PermalinkIndexUpdateCommand extends ApplicationEvent { - private final ExtensionLocator locator; - private final String permalink; - - public PermalinkIndexUpdateCommand(Object source, ExtensionLocator locator, String permalink) { - super(source); - this.locator = locator; - this.permalink = permalink; - } - - public ExtensionLocator getLocator() { - return locator; - } - - public String getPermalink() { - return permalink; - } -} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkIndexer.java b/src/main/java/run/halo/app/theme/router/PermalinkIndexer.java deleted file mode 100644 index 4657a6feef0..00000000000 --- a/src/main/java/run/halo/app/theme/router/PermalinkIndexer.java +++ /dev/null @@ -1,251 +0,0 @@ -package run.halo.app.theme.router; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationContext; -import org.springframework.context.event.EventListener; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; -import run.halo.app.content.permalinks.ExtensionLocator; -import run.halo.app.extension.GroupVersionKind; - -/** - *

Permalink indexer for lookup extension's name and slug by permalink.

- * - * @author guqing - * @since 2.0.0 - */ -@Slf4j -@Component -public class PermalinkIndexer { - private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - - private final Map gvkNamePermalinkLookup = new HashMap<>(); - private final Map permalinkLocatorLookup = new HashMap<>(); - - private final ApplicationContext applicationContext; - - public PermalinkIndexer(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - - /** - * Register extension and permalink mapping. - * - * @param locator extension locator to hold the gvk and name and slug - * @param permalink extension permalink for template route - */ - public void register(ExtensionLocator locator, String permalink) { - readWriteLock.writeLock().lock(); - try { - GvkName gvkName = new GvkName(locator.gvk(), locator.name()); - gvkNamePermalinkLookup.put(gvkName, permalink); - permalinkLocatorLookup.put(permalink, locator); - publishEvent(gvkName, null, permalink); - } finally { - readWriteLock.writeLock().unlock(); - } - } - - private void publishEvent(GvkName gvkName, String oldPermalink, String newPermalink) { - applicationContext.publishEvent( - new PermalinkIndexChangedEvent(this, gvkName, oldPermalink, newPermalink)); - } - - /** - * Remove extension and permalink mapping. - * - * @param locator extension info - */ - public void remove(ExtensionLocator locator) { - readWriteLock.writeLock().lock(); - try { - GvkName gvkName = new GvkName(locator.gvk(), locator.name()); - String permalink = gvkNamePermalinkLookup.remove(gvkName); - if (permalink != null) { - permalinkLocatorLookup.remove(permalink); - publishEvent(gvkName, permalink, null); - } - } finally { - readWriteLock.writeLock().unlock(); - } - } - - /** - * Gets permalink by {@link GroupVersionKind}. - * - * @param gvk group version kind - * @return permalinks - */ - @NonNull - public List getPermalinks(GroupVersionKind gvk) { - readWriteLock.readLock().lock(); - try { - return gvkNamePermalinkLookup.entrySet() - .stream() - .filter(entry -> entry.getKey().gvk().equals(gvk)) - .map(Map.Entry::getValue) - .toList(); - } finally { - readWriteLock.readLock().unlock(); - } - } - - /** - * Lookup extension info by permalink. - * - * @param permalink extension permalink for theme template route - * @return extension locator - */ - @Nullable - public ExtensionLocator lookup(String permalink) { - readWriteLock.readLock().lock(); - try { - return permalinkLocatorLookup.get(permalink); - } finally { - readWriteLock.readLock().unlock(); - } - } - - /** - * Lookup extension permalink by {@link GroupVersionKind} and {@code name}. - * - * @param gvk group version kind - * @param name extension name - * @return {@code true} if contains, otherwise {@code false} - */ - public boolean containsName(GroupVersionKind gvk, String name) { - readWriteLock.readLock().lock(); - try { - return gvkNamePermalinkLookup.containsKey(new GvkName(gvk, name)); - } finally { - readWriteLock.readLock().unlock(); - } - } - - /** - * Lookup extension permalink by {@link GroupVersionKind} and {@code slug}. - * - * @param gvk group version kind - * @param slug extension slug - * @return {@code true} if contains, otherwise {@code false} - */ - public boolean containsSlug(GroupVersionKind gvk, String slug) { - readWriteLock.readLock().lock(); - try { - return permalinkLocatorLookup.values() - .stream() - .anyMatch(locator -> locator.gvk().equals(gvk) - && locator.slug().equals(slug)); - } finally { - readWriteLock.readLock().unlock(); - } - } - - /** - * Lookup extension name by resource slug. - * - * @param gvk extension's {@link GroupVersionKind} - * @param slug extension resource slug - * @return extension resource name specified by resource slug - */ - public String getNameBySlug(GroupVersionKind gvk, String slug) { - readWriteLock.readLock().lock(); - try { - return permalinkLocatorLookup.values() - .stream() - .filter(locator -> locator.gvk().equals(gvk) - && locator.slug().equals(slug)) - .findFirst() - .map(ExtensionLocator::name) - .orElseThrow(); - } finally { - readWriteLock.readLock().unlock(); - } - } - - /** - * Get extension name by permalink. - * - * @param gvk is GroupVersionKind of extension - * @param permalink is encoded permalink - * @return extension name or null - */ - @Nullable - public String getNameByPermalink(GroupVersionKind gvk, String permalink) { - readWriteLock.readLock().lock(); - try { - var locator = permalinkLocatorLookup.get(permalink); - return locator == null ? null : locator.name(); - } finally { - readWriteLock.readLock().unlock(); - } - } - - /** - * Only for test. - * - * @return permalinkLookup map size - */ - protected long gvkNamePermalinkMapSize() { - return gvkNamePermalinkLookup.size(); - } - - /** - * Only for test. - * - * @return permalinkLocatorMap map size - */ - protected long permalinkLocatorMapSize() { - return permalinkLocatorLookup.size(); - } - - /** - * Add a record to the {@link PermalinkIndexer}. - * If permalink already exists, it will not be added to indexer - * - * @param addCommand a command to add a record to {@link PermalinkIndexer} - */ - @EventListener(PermalinkIndexAddCommand.class) - public void onPermalinkAdd(PermalinkIndexAddCommand addCommand) { - if (checkPermalinkExists(addCommand.getLocator(), addCommand.getPermalink())) { - // TODO send an extension event to log this error - log.error("Permalink [{}] already exists, you can try to change the slug [{}].", - addCommand.getPermalink(), addCommand.getLocator()); - return; - } - register(addCommand.getLocator(), addCommand.getPermalink()); - } - - @EventListener(PermalinkIndexDeleteCommand.class) - public void onPermalinkDelete(PermalinkIndexDeleteCommand deleteCommand) { - remove(deleteCommand.getLocator()); - } - - /** - * Update a {@link PermalinkIndexer} record by {@link ExtensionLocator} and permalink. - * If permalink already exists, it will not be updated - * - * @param updateCommand a command to update an indexer record - */ - @EventListener(PermalinkIndexUpdateCommand.class) - public void onPermalinkUpdate(PermalinkIndexUpdateCommand updateCommand) { - if (checkPermalinkExists(updateCommand.getLocator(), updateCommand.getPermalink())) { - // TODO send an extension event to log this error - log.error("Permalink [{}] already exists, you can try to change the slug [{}].", - updateCommand.getPermalink(), updateCommand.getLocator()); - return; - } - remove(updateCommand.getLocator()); - register(updateCommand.getLocator(), updateCommand.getPermalink()); - } - - private boolean checkPermalinkExists(ExtensionLocator locator, String permalink) { - ExtensionLocator lookup = lookup(permalink); - return lookup != null && !lookup.equals(locator); - } -} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkPatternProvider.java b/src/main/java/run/halo/app/theme/router/PermalinkPatternProvider.java deleted file mode 100644 index 4e731915e51..00000000000 --- a/src/main/java/run/halo/app/theme/router/PermalinkPatternProvider.java +++ /dev/null @@ -1,60 +0,0 @@ -package run.halo.app.theme.router; - -import java.util.Map; -import org.springframework.stereotype.Component; -import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -import run.halo.app.infra.SystemSetting; -import run.halo.app.infra.utils.JsonUtils; -import run.halo.app.theme.DefaultTemplateEnum; - -/** - *

The {@link PermalinkPatternProvider} used to obtain permalink rules according to specific - * template names.

- * - * @author guqing - * @since 2.0.0 - */ -@Component -public class PermalinkPatternProvider { - - private final SystemConfigurableEnvironmentFetcher environmentFetcher; - - public PermalinkPatternProvider(SystemConfigurableEnvironmentFetcher environmentFetcher) { - this.environmentFetcher = environmentFetcher; - } - - private SystemSetting.ThemeRouteRules getPermalinkRules() { - return environmentFetcher.getConfigMapBlocking() - .map(configMap -> { - Map data = configMap.getData(); - return data.get(SystemSetting.ThemeRouteRules.GROUP); - }) - .map(routeRulesJson -> JsonUtils.jsonToObject(routeRulesJson, - SystemSetting.ThemeRouteRules.class)) - .orElseGet(() -> { - SystemSetting.ThemeRouteRules themeRouteRules = new SystemSetting.ThemeRouteRules(); - themeRouteRules.setArchives("archives"); - themeRouteRules.setPost("/archives/{slug}"); - themeRouteRules.setTags("tags"); - themeRouteRules.setCategories("categories"); - return themeRouteRules; - }); - } - - /** - * Get permalink pattern by template name. - * - * @param defaultTemplateEnum default templates - * @return a pattern specified by the template name - */ - public String getPattern(DefaultTemplateEnum defaultTemplateEnum) { - SystemSetting.ThemeRouteRules permalinkRules = getPermalinkRules(); - return switch (defaultTemplateEnum) { - case INDEX, SINGLE_PAGE, AUTHOR -> null; - case POST -> permalinkRules.getPost(); - case ARCHIVES -> permalinkRules.getArchives(); - case CATEGORY, CATEGORIES -> permalinkRules.getCategories(); - case TAG, TAGS -> permalinkRules.getTags(); - }; - } -} diff --git a/src/main/java/run/halo/app/theme/router/PermalinkRefreshHandler.java b/src/main/java/run/halo/app/theme/router/PermalinkRefreshHandler.java deleted file mode 100644 index 141d4dfad73..00000000000 --- a/src/main/java/run/halo/app/theme/router/PermalinkRefreshHandler.java +++ /dev/null @@ -1,107 +0,0 @@ -package run.halo.app.theme.router; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.context.ApplicationListener; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Component; -import run.halo.app.content.permalinks.CategoryPermalinkPolicy; -import run.halo.app.content.permalinks.PostPermalinkPolicy; -import run.halo.app.content.permalinks.TagPermalinkPolicy; -import run.halo.app.core.extension.content.Category; -import run.halo.app.core.extension.content.Post; -import run.halo.app.core.extension.content.Tag; -import run.halo.app.extension.ExtensionClient; -import run.halo.app.theme.DefaultTemplateEnum; - -/** - * Permalink refresh handler. - * - * @author guqing - * @since 2.0.0 - */ -@Slf4j -@Component -public class PermalinkRefreshHandler implements ApplicationListener { - private final ExtensionClient client; - private final PostPermalinkPolicy postPermalinkPolicy; - private final TagPermalinkPolicy tagPermalinkPolicy; - private final CategoryPermalinkPolicy categoryPermalinkPolicy; - - public PermalinkRefreshHandler(ExtensionClient client, - PostPermalinkPolicy postPermalinkPolicy, - TagPermalinkPolicy tagPermalinkPolicy, - CategoryPermalinkPolicy categoryPermalinkPolicy) { - this.client = client; - this.postPermalinkPolicy = postPermalinkPolicy; - this.tagPermalinkPolicy = tagPermalinkPolicy; - this.categoryPermalinkPolicy = categoryPermalinkPolicy; - } - - @Override - public void onApplicationEvent(@NonNull PermalinkRuleChangedEvent event) { - DefaultTemplateEnum template = event.getTemplate(); - log.debug("Refresh permalink for template [{}]", template.getValue()); - switch (template) { - case POST -> updatePostPermalink(); - case CATEGORIES, CATEGORY -> updateCategoryPermalink(); - case TAGS, TAG -> updateTagPermalink(); - default -> { - } - } - } - - private void updatePostPermalink() { - String pattern = postPermalinkPolicy.pattern(); - log.debug("Update post permalink by new policy [{}]", pattern); - client.list(Post.class, null, null) - .forEach(post -> { - String oldPermalink = post.getStatusOrDefault().getPermalink(); - String permalink = postPermalinkPolicy.permalink(post); - post.getStatusOrDefault().setPermalink(permalink); - if (StringUtils.equals(oldPermalink, permalink)) { - return; - } - // update permalink - client.update(post); - - postPermalinkPolicy.onPermalinkUpdate(post); - }); - } - - private void updateCategoryPermalink() { - String pattern = categoryPermalinkPolicy.pattern(); - log.debug("Update category and categories permalink by new policy [{}]", pattern); - client.list(Category.class, null, null) - .forEach(category -> { - String oldPermalink = category.getStatusOrDefault().getPermalink(); - String permalink = categoryPermalinkPolicy.permalink(category); - category.getStatusOrDefault().setPermalink(permalink); - if (StringUtils.equals(oldPermalink, permalink)) { - return; - } - // update permalink - client.update(category); - - categoryPermalinkPolicy.onPermalinkUpdate(category); - }); - } - - private void updateTagPermalink() { - String pattern = tagPermalinkPolicy.pattern(); - log.debug("Update tag and tags permalink by new policy [{}]", pattern); - client.list(Tag.class, null, null) - .forEach(tag -> { - String oldPermalink = tag.getStatusOrDefault().getPermalink(); - String permalink = tagPermalinkPolicy.permalink(tag); - tag.getStatusOrDefault().setPermalink(permalink); - if (StringUtils.equals(oldPermalink, permalink)) { - return; - } - // update permalink - client.update(tag); - - tagPermalinkPolicy.onPermalinkUpdate(tag); - }); - } -} diff --git a/src/main/java/run/halo/app/theme/router/RadixRouterTree.java b/src/main/java/run/halo/app/theme/router/RadixRouterTree.java deleted file mode 100644 index e6c18136124..00000000000 --- a/src/main/java/run/halo/app/theme/router/RadixRouterTree.java +++ /dev/null @@ -1,224 +0,0 @@ -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; -import org.springframework.web.util.pattern.PathPatternParser; - -/** - * A router tree implementation based on radix tree. - * - * @author guqing - * @since 2.0.0 - */ -@Slf4j -public class RadixRouterTree extends RadixTree> { - - @Override - public void insert(String key, HandlerFunction value) - throws IllegalArgumentException { - super.insert(key, value); - if (log.isDebugEnabled()) { - checkIndices(); - } - } - - @Override - public boolean delete(String key) { - boolean result = super.delete(key); - if (log.isDebugEnabled()) { - checkIndices(); - } - return result; - } - - /** - *

Find the tree node according to the request path.

- * There are the following situations: - *
    - *
  • If found, return to the {@link HandlerFunction} directly.
  • - *
  • If the request path is not found in tree, it may be this request path corresponds - * to a pattern,so all the patterns will be iterated to match the current request path. If - * they match, the handler will be returned. - * Otherwise, null will be returned.
  • - *
- * for example, there is a tree as follows: - *
-     * / [indices=tca, priority=4]
-     * ├── tags/ [indices=h{, priority=2]
-     * │   ├── halo [value=tags-halo, priority=1]*
-     * │   └── {slug}/page/{page} [value=post by tags, priority=1]*
-     * ├── categories/default [value=categories-default, priority=1]*
-     * └── about [value=about, priority=1]*
-     * 
- *

1. find the request path "/categories/default" in tree, return the handler directly.

- *

2. but find the request path "/categories/default/page/1", it will be iterated to - * match

- * TODO Optimize matching algorithm to improve efficiency and try your best to get results - * through one search - * - * @param request server request - * @return a handler function if matched, otherwise null - */ - public HandlerFunction match(ServerRequest request) { - String path = pathToFind(request); - HandlerFunction result = find(path); - if (result != null) { - return result; - } - - PathContainer pathContainer = PathContainer.parsePath(path); - - List matches = new ArrayList<>(); - for (String pathPattern : getKeys()) { - if (!hasPatternSyntax(pathPattern)) { - continue; - } - log.trace("PathPatternParser handle pathPattern [{}]", pathPattern); - PathPattern parse = PathPatternParser.defaultInstance.parse(pathPattern); - if (parse.matches(pathContainer)) { - matches.add(parse); - } - } - - if (matches.isEmpty()) { - return null; - } - matches.sort(PathPattern.SPECIFICITY_COMPARATOR); - PathPattern bestMatch = matches.get(0); - if (matches.size() > 1) { - if (log.isTraceEnabled()) { - log.trace("request [GET {}] matching mappings: [{}]", path, matches); - } - PathPattern secondBestMatch = matches.get(1); - if (PathPattern.SPECIFICITY_COMPARATOR.compare(bestMatch, secondBestMatch) == 0) { - throw new IllegalStateException( - "Ambiguous mapping mapped for '" + path + "': {" + bestMatch + ", " - + 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 路径的一部分。 - */ - static 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); - } - } - requestPath = StringUtils.removeEnd(requestPath, "/"); - 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 static String processRequestPath(String requestPath) { - String path = StringUtils.prependIfMissing(requestPath, "/"); - return UriUtils.decode(path, StandardCharsets.UTF_8); - } - - public boolean hasPatternSyntax(String pathPattern) { - return pathPattern.indexOf('{') != -1 || pathPattern.indexOf(':') != -1 - || pathPattern.indexOf('*') != -1; - } - - /** - * Get all keys(paths) in trie, call recursion function - * Time O(n), Space O(n), n is number of nodes in trie. - */ - public List getKeys() { - List res = new ArrayList<>(); - keysHelper(root, res, ""); - return res; - } - - /** - * Similar to pre-order (DFS, depth first search) of the tree, - * recursion is used to traverse all nodes in trie. When visiting the node, - * the method concatenates characters from previously visited nodes with - * the character of the current node. When the node's isReal is true, - * the recursion reaches the last character of the path. - * Add the path to the result list. - * recursion function, Time O(n), Space O(n), n is number of nodes in trie - */ - void keysHelper(RadixTreeNode> node, List res, - String prefix) { - if (node == null) { - //base condition - return; - } - - if (node.isReal()) { - String path = prefix + node.getKey(); - res.add(path); - } - for (RadixTreeNode> child : node.getChildren()) { - keysHelper(child, res, prefix + node.getKey()); - } - } - -} diff --git a/src/main/java/run/halo/app/theme/router/RadixTree.java b/src/main/java/run/halo/app/theme/router/RadixTree.java deleted file mode 100644 index ee9b80fa9f3..00000000000 --- a/src/main/java/run/halo/app/theme/router/RadixTree.java +++ /dev/null @@ -1,360 +0,0 @@ -package run.halo.app.theme.router; - -import java.util.ArrayList; -import java.util.Iterator; -import lombok.Data; - -/** - * Implementation for {@link RadixTree Radix tree}. - * - * @author guqing - */ -@Data -public class RadixTree { - protected RadixTreeNode root; - protected long size; - - /** - * Create a Radix Tree with only the default node root. - */ - public RadixTree() { - root = new RadixTreeNode(); - root.setKey("/"); - root.setIndices(""); - size = 0; - } - - /** - * Find the node value with the given key. - * - * @param key the key to search - * @return value of the node with the given key if found, otherwise null - */ - public T find(String key) { - Visitor visitor = new Visitor<>() { - public void visit(String key, RadixTreeNode parent, - RadixTreeNode node) { - if (node.isReal()) { - result = node.getValue(); - } - } - }; - visit(key, visitor); - return visitor.getResult(); - } - - /** - * Replace value by the given key. - * - * @param key the key to search - * @param value the value to replace - * @return {@code true} if replaced, otherwise {@code false} - */ - public boolean replace(String key, final T value) { - Visitor visitor = new Visitor<>() { - public void visit(String key, RadixTreeNode parent, RadixTreeNode node) { - if (node.isReal()) { - node.setValue(value); - result = value; - } else { - result = null; - } - } - }; - visit(key, visitor); - return visitor.getResult() != null; - } - - /** - * Delete the tree node with the given key. - * - * @param key the key to delete - * @return @{code true} if deleted, otherwise {@code false} - */ - public boolean delete(String key) { - Visitor visitor = new Visitor<>(Boolean.FALSE) { - public void visit(String key, RadixTreeNode parent, - RadixTreeNode node) { - result = node.isReal(); - // if it is a real node - if (result) { - // If there are no children of the node we need to - // delete it from the parent children list - if (node.getChildren().size() == 0) { - Iterator> it = parent.getChildren() - .iterator(); - for (int index = 0; it.hasNext(); index++) { - if (it.next().getKey().equals(node.getKey())) { - // delete node - it.remove(); - - // update indices - StringBuilder indices = new StringBuilder(parent.getIndices()); - indices.deleteCharAt(index); - parent.setIndices(indices.toString()); - break; - } - } - - // if parent is not real node and has only one child - // then they need to be merged. - if (parent.getChildren().size() == 1 - && !parent.isReal()) { - mergeNodes(parent, parent.getChildren().get(0)); - } - } else if (node.getChildren().size() == 1) { - // we need to merge the only child of this node with - // itself - mergeNodes(node, node.getChildren().get(0)); - } else { // we jus need to mark the node as non-real. - node.setReal(false); - } - } - } - - /** - * Merge a child into its parent node. Operation only valid if it is - * only child of the parent node and parent node is not a real node. - * - * @param parent The parent Node - * @param child The child Node - */ - private void mergeNodes(RadixTreeNode parent, - RadixTreeNode child) { - parent.setKey(parent.getKey() + child.getKey()); - parent.setReal(child.isReal()); - parent.setValue(child.getValue()); - parent.setChildren(child.getChildren()); - parent.setIndices(child.getIndices()); - } - }; - visit(key, visitor); - if (visitor.getResult()) { - size--; - } - return visitor.getResult(); - } - - /** - * Recursively insert the key in the radix tree. - * - * @see #insert(String, Object) - */ - public void insert(String key, T value) throws IllegalArgumentException { - try { - insert(key, root, value); - } catch (IllegalArgumentException e) { - // re-throw the exception with 'key' in the message - throw new IllegalArgumentException("A handle is already registered for key:" + key); - } - size++; - } - - /** - * Recursively insert the key in the radix tree. - * - * @param key The key to be inserted - * @param node The current node - * @param value The value associated with the key - * @throws IllegalArgumentException If the key already exists in the database. - */ - private void insert(String key, RadixTreeNode node, T value) - throws IllegalArgumentException { - int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key); - // we are either at the root node - // or we need to go down the tree - if (node.getKey().equals("") || numberOfMatchingCharacters == 0 - || (numberOfMatchingCharacters < key.length() - && numberOfMatchingCharacters >= node.getKey().length())) { - boolean flag = false; - String newText = key.substring(numberOfMatchingCharacters); - - // 递归查找插入位置 - char idxc = newText.charAt(0); - for (int i = 0; i < node.getIndices().length(); i++) { - if (node.getIndices().charAt(i) == idxc) { - RadixTreeNode child = node.getChildren().get(i); - flag = true; - insert(newText, child, value); - break; - } - } - // just add the node as the child of the current node - if (!flag) { - RadixTreeNode n = new RadixTreeNode(); - n.setKey(newText); - n.setReal(true); - n.setValue(value); - // 往后追加与child对于的首字母到 indices - node.setIndices(node.getIndices() + idxc); - node.getChildren().add(n); - } - } else if (numberOfMatchingCharacters == key.length() - && numberOfMatchingCharacters == node.getKey().length()) { - // there is an exact match just make the current node as data node - if (node.isReal()) { - throw new IllegalArgumentException("Duplicate key."); - } - node.setReal(true); - node.setValue(value); - } else if (numberOfMatchingCharacters > 0 && numberOfMatchingCharacters < node.getKey() - .length()) { - // This node need to be split as the key to be inserted - // is a prefix of the current node key - RadixTreeNode n1 = new RadixTreeNode<>(); - n1.setKey(node.getKey().substring(numberOfMatchingCharacters)); - n1.setReal(node.isReal()); - n1.setValue(node.getValue()); - n1.setIndices(node.getIndices()); - n1.setChildren(node.getChildren()); - - node.setKey(key.substring(0, numberOfMatchingCharacters)); - node.setReal(false); - node.setChildren(new ArrayList<>()); - node.getChildren().add(n1); - node.setIndices(""); - // 往后追加与child对于的首字母到 indices - node.setIndices(node.getIndices() + n1.getKey().charAt(0)); - // 新公共前缀比原公共前缀短,需要将当前的节点按公共前缀分开 - if (numberOfMatchingCharacters < key.length()) { - RadixTreeNode n2 = new RadixTreeNode<>(); - n2.setKey(key.substring(numberOfMatchingCharacters)); - n2.setReal(true); - n2.setValue(value); - - node.getChildren().add(n2); - node.setIndices(node.getIndices() + n2.getKey().charAt(0)); - } else { - node.setValue(value); - node.setReal(true); - } - } else { - // this key need to be added as the child of the current node - RadixTreeNode n = new RadixTreeNode(); - n.setKey(node.getKey().substring(numberOfMatchingCharacters)); - n.setChildren(node.getChildren()); - n.setReal(node.isReal()); - n.setValue(node.getValue()); - - node.setKey(key); - node.setReal(true); - node.setValue(value); - node.getChildren().add(n); - char idxc = node.getKey().charAt(0); - // 往后追加与child对于的首字母到 indices - n.setIndices(n.getIndices() + idxc); - } - } - - /** - * The tree contains the key. - * - * @param key the key to search - * @return {@code true} if the tree contains the key, otherwise {@code false} - */ - public boolean contains(String key) { - Visitor visitor = new Visitor<>(Boolean.FALSE) { - public void visit(String key, RadixTreeNode parent, - RadixTreeNode node) { - result = node.isReal(); - } - }; - visit(key, visitor); - return visitor.getResult(); - } - - /** - * visit the node those key matches the given key. - * - * @param key The key that need to be visited - * @param visitor The visitor object - */ - public void visit(String key, Visitor visitor) { - if (root != null) { - visit(key, visitor, null, root); - } - } - - /** - * recursively visit the tree based on the supplied "key". calls the Visitor - * for the node those key matches the given prefix. - * - * @param prefix The key of prefix to search in the tree - * @param visitor The Visitor that will be called if a node with "key" as its key is found - * @param node The Node from where onward to search - */ - void visit(String prefix, Visitor visitor, - RadixTreeNode parent, RadixTreeNode node) { - int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(prefix); - // if the node key and prefix match, we found a match! - if (numberOfMatchingCharacters == prefix.length() - && numberOfMatchingCharacters == node.getKey().length()) { - visitor.visit(prefix, parent, node); - } else if (node.getKey().equals("") // either we are at the - // root - || (numberOfMatchingCharacters < prefix.length() - && numberOfMatchingCharacters >= node.getKey().length())) { - // OR we need to traverse the children - String newText = prefix.substring(numberOfMatchingCharacters); - for (RadixTreeNode child : node.getChildren()) { - // recursively search the child nodes - if (child.getKey().startsWith(newText.charAt(0) + "")) { - visit(newText, visitor, node, child); - break; - } - } - } - } - - public long getSize() { - return size; - } - - /** - *

Display the Trie on console.

- * WARNING! Do not use this for a large Trie, it's for testing purpose only. - */ - @Deprecated - public String display() { - StringBuilder buffer = new StringBuilder(); - root.print(buffer, "", ""); - return buffer.toString(); - } - - /** - * Only used for testing purpose. - */ - public void checkIndices() { - this.checkIndices(root); - } - - void checkIndices(RadixTreeNode node) { - if (node == null) { - //base condition - return; - } - node.checkIndices(); - for (RadixTreeNode child : node.getChildren()) { - checkIndices(child); - } - } - - public abstract static class Visitor { - - protected R result; - - public Visitor() { - this.result = null; - } - - public Visitor(R initialValue) { - this.result = initialValue; - } - - public R getResult() { - return result; - } - - public abstract void visit(String key, RadixTreeNode parent, RadixTreeNode node); - } -} diff --git a/src/main/java/run/halo/app/theme/router/RadixTreeNode.java b/src/main/java/run/halo/app/theme/router/RadixTreeNode.java deleted file mode 100644 index 33512e4dbd2..00000000000 --- a/src/main/java/run/halo/app/theme/router/RadixTreeNode.java +++ /dev/null @@ -1,85 +0,0 @@ -package run.halo.app.theme.router; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import lombok.Data; -import org.apache.commons.lang3.StringUtils; - -/** - * Represents a node of a Radix tree {@link RadixTree}. - * - * @param value type - * @author guqing - */ -@Data -public class RadixTreeNode { - private String key; - private List> children; - private boolean real; - private T value; - - protected String indices; - - /** - * intailize the fields with default values to avoid null reference checks - * all over the places. - */ - public RadixTreeNode() { - key = ""; - children = new ArrayList<>(); - real = false; - indices = ""; - } - - protected int getNumberOfMatchingCharacters(String key) { - int numberOfMatchingCharacters = 0; - while (numberOfMatchingCharacters < key.length() - && numberOfMatchingCharacters < this.getKey().length()) { - if (key.charAt(numberOfMatchingCharacters) != this.getKey() - .charAt(numberOfMatchingCharacters)) { - break; - } - numberOfMatchingCharacters++; - } - return numberOfMatchingCharacters; - } - - void print(StringBuilder buffer, String prefix, String childrenPrefix) { - buffer.append(prefix); - buffer.append(printNode()); - buffer.append('\n'); - for (Iterator> it = children.iterator(); it.hasNext(); ) { - RadixTreeNode next = it.next(); - if (it.hasNext()) { - next.print(buffer, childrenPrefix + "├── ", childrenPrefix + "│ "); - } else { - next.print(buffer, childrenPrefix + "└── ", childrenPrefix + " "); - } - } - } - - String printNode() { - if (isReal()) { - return String.format("%s [value=%s]*", getKey(), getValue()); - } else { - return String.format("%s [indices=%s]", getKey(), getIndices()); - } - } - - /** - * Check whether {@link #indices} matches the {@link #children} items prefix. - */ - public void checkIndices() { - StringBuilder indices = new StringBuilder(); - for (RadixTreeNode child : this.getChildren()) { - indices.append(child.getKey().charAt(0)); - } - - if (!StringUtils.equals(this.getIndices(), indices.toString())) { - throw new IllegalStateException( - String.format("indices mismatch for node '%s': is %s, should be %s", this.getKey(), - this.getIndices(), indices)); - } - } -} diff --git a/src/main/java/run/halo/app/theme/router/ThemeCompositeRouterFunction.java b/src/main/java/run/halo/app/theme/router/ThemeCompositeRouterFunction.java index 3548f979ec2..429c5ca2cf8 100644 --- a/src/main/java/run/halo/app/theme/router/ThemeCompositeRouterFunction.java +++ b/src/main/java/run/halo/app/theme/router/ThemeCompositeRouterFunction.java @@ -1,40 +1,133 @@ package run.halo.app.theme.router; -import org.springframework.http.HttpMethod; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.event.EventListener; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.RouterFunction; +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 reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import run.halo.app.infra.SchemeInitializedEvent; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; +import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.router.factories.ArchiveRouteFactory; +import run.halo.app.theme.router.factories.AuthorPostsRouteFactory; +import run.halo.app.theme.router.factories.CategoriesRouteFactory; +import run.halo.app.theme.router.factories.CategoryPostRouteFactory; +import run.halo.app.theme.router.factories.IndexRouteFactory; +import run.halo.app.theme.router.factories.PostRouteFactory; +import run.halo.app.theme.router.factories.SinglePageRoute; +import run.halo.app.theme.router.factories.TagPostRouteFactory; +import run.halo.app.theme.router.factories.TagsRouteFactory; /** - *

Theme template composite {@link RouterFunction} for manage routers for default templates.

- * It routes specific requests to the {@link RouterFunction} maintained by the - * {@link PermalinkHttpGetRouter}. + *

The combination router of theme templates is used to render theme templates, but does not + * include page.html templates which is processed separately.

* * @author guqing - * @see PermalinkHttpGetRouter + * @see SinglePageRoute * @since 2.0.0 */ @Component -public class ThemeCompositeRouterFunction implements - RouterFunction { +@RequiredArgsConstructor +public class ThemeCompositeRouterFunction implements RouterFunction { + private final SystemConfigurableEnvironmentFetcher environmentFetcher; - private final PermalinkHttpGetRouter permalinkHttpGetRouter; + private final ArchiveRouteFactory archiveRouteFactory; + private final PostRouteFactory postRouteFactory; + private final CategoriesRouteFactory categoriesRouteFactory; + private final CategoryPostRouteFactory categoryPostRouteFactory; + private final TagPostRouteFactory tagPostRouteFactory; + private final TagsRouteFactory tagsRouteFactory; + private final AuthorPostsRouteFactory authorPostsRouteFactory; + private final IndexRouteFactory indexRouteFactory; - public ThemeCompositeRouterFunction(PermalinkHttpGetRouter permalinkHttpGetRouter) { - this.permalinkHttpGetRouter = permalinkHttpGetRouter; - } + private List> cachedRouters = List.of(); @Override @NonNull public Mono> route(@NonNull ServerRequest request) { - // this router function only supports GET method - if (!request.method().equals(HttpMethod.GET)) { - return Mono.empty(); - } - return Mono.justOrEmpty(permalinkHttpGetRouter.route(request)); + return Flux.fromIterable(cachedRouters) + .concatMap(routerFunction -> routerFunction.route(request)) + .next(); + } + + @Override + public void accept(@NonNull RouterFunctions.Visitor visitor) { + cachedRouters.forEach(routerFunction -> routerFunction.accept(visitor)); + } + + List> routerFunctions() { + return transformedPatterns() + .stream() + .map(this::createRouterFunction) + .collect(Collectors.toList()); + } + + private RouterFunction createRouterFunction(RoutePattern routePattern) { + return switch (routePattern.identifier()) { + case POST -> postRouteFactory.create(routePattern.pattern()); + case ARCHIVES -> archiveRouteFactory.create(routePattern.pattern()); + case CATEGORIES -> categoriesRouteFactory.create(routePattern.pattern()); + case CATEGORY -> categoryPostRouteFactory.create(routePattern.pattern()); + case TAGS -> tagsRouteFactory.create(routePattern.pattern()); + case TAG -> tagPostRouteFactory.create(routePattern.pattern()); + case AUTHOR -> authorPostsRouteFactory.create(routePattern.pattern()); + case INDEX -> indexRouteFactory.create(routePattern.pattern()); + default -> + throw new IllegalStateException("Unexpected value: " + routePattern.identifier()); + }; + } + + /** + * Refresh the {@link #cachedRouters} when the permalink rule is changed. + * + * @param event {@link SchemeInitializedEvent} or {@link PermalinkRuleChangedEvent} + */ + @EventListener({SchemeInitializedEvent.class, PermalinkRuleChangedEvent.class}) + public void onSchemeInitializedEvent(@NonNull ApplicationEvent event) { + this.cachedRouters = routerFunctions(); + } + + record RoutePattern(DefaultTemplateEnum identifier, String pattern) { + } + + private List transformedPatterns() { + List routePatterns = new ArrayList<>(); + + SystemSetting.ThemeRouteRules rules = + environmentFetcher.fetch(SystemSetting.ThemeRouteRules.GROUP, + SystemSetting.ThemeRouteRules.class) + .blockOptional() + .orElse(SystemSetting.ThemeRouteRules.empty()); + String post = rules.getPost(); + routePatterns.add(new RoutePattern(DefaultTemplateEnum.POST, post)); + + String archives = rules.getArchives(); + routePatterns.add( + new RoutePattern(DefaultTemplateEnum.ARCHIVES, archives)); + + String categories = rules.getCategories(); + routePatterns.add( + new RoutePattern(DefaultTemplateEnum.CATEGORIES, categories)); + routePatterns.add( + new RoutePattern(DefaultTemplateEnum.CATEGORY, categories)); + + String tags = rules.getTags(); + routePatterns.add(new RoutePattern(DefaultTemplateEnum.TAGS, tags)); + routePatterns.add(new RoutePattern(DefaultTemplateEnum.TAG, tags)); + + // Add the index route to the end to prevent conflict with the queryParam rule of the post + routePatterns.add(new RoutePattern(DefaultTemplateEnum.INDEX, "/")); + return routePatterns; } } diff --git a/src/main/java/run/halo/app/theme/router/poc/ArchiveRouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/ArchiveRouteFactory.java similarity index 89% rename from src/main/java/run/halo/app/theme/router/poc/ArchiveRouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/ArchiveRouteFactory.java index 6a9b372f3a9..62f4bf0d329 100644 --- a/src/main/java/run/halo/app/theme/router/poc/ArchiveRouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/ArchiveRouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; import static run.halo.app.theme.router.PageUrlUtils.totalPage; @@ -26,7 +26,6 @@ import run.halo.app.theme.finders.vo.PostArchiveVo; import run.halo.app.theme.router.PageUrlUtils; import run.halo.app.theme.router.UrlContextListResult; -import run.halo.app.theme.router.strategy.ModelConst; /** * The {@link ArchiveRouteFactory} for generate {@link RouterFunction} specific to the template @@ -47,16 +46,20 @@ public class ArchiveRouteFactory implements RouteFactory { public RouterFunction create(String prefix) { RequestPredicate requestPredicate = patterns(prefix).stream() .map(RequestPredicates::GET) - .reduce(RequestPredicates.all(), RequestPredicate::or) + .reduce(req -> false, RequestPredicate::or) .and(accept(MediaType.TEXT_HTML)); return RouterFunctions.route(requestPredicate, handlerFunction()); } HandlerFunction handlerFunction() { - return request -> ServerResponse.ok() - .render(DefaultTemplateEnum.INDEX.getValue(), - Map.of("posts", archivePosts(request), - ModelConst.TEMPLATE_ID, DefaultTemplateEnum.INDEX.getValue())); + return request -> { + String templateName = DefaultTemplateEnum.ARCHIVES.getValue(); + return ServerResponse.ok() + .render(templateName, + Map.of("archives", archivePosts(request), + ModelConst.TEMPLATE_ID, templateName) + ); + }; } private List patterns(String prefix) { diff --git a/src/main/java/run/halo/app/theme/router/poc/AuthorPostsRouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/AuthorPostsRouteFactory.java similarity index 95% rename from src/main/java/run/halo/app/theme/router/poc/AuthorPostsRouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/AuthorPostsRouteFactory.java index 4934bef79e5..553c6b54dbe 100644 --- a/src/main/java/run/halo/app/theme/router/poc/AuthorPostsRouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/AuthorPostsRouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -23,7 +23,6 @@ import run.halo.app.theme.finders.vo.UserVo; import run.halo.app.theme.router.PageUrlUtils; import run.halo.app.theme.router.UrlContextListResult; -import run.halo.app.theme.router.strategy.ModelConst; /** * The {@link AuthorPostsRouteFactory} for generate {@link RouterFunction} specific to the template @@ -73,7 +72,7 @@ private Mono> postList(ServerRequest request, } private Mono getByName(String name) { - return client.fetch(User.class, name) + return client.get(User.class, name) .map(UserVo::from); } } diff --git a/src/main/java/run/halo/app/theme/router/poc/CategoriesPostRouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/CategoriesRouteFactory.java similarity index 84% rename from src/main/java/run/halo/app/theme/router/poc/CategoriesPostRouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/CategoriesRouteFactory.java index afedb03a50f..c16f2976ebb 100644 --- a/src/main/java/run/halo/app/theme/router/poc/CategoriesPostRouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/CategoriesRouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; @@ -12,10 +12,9 @@ import org.springframework.web.reactive.function.server.ServerResponse; import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.finders.CategoryFinder; -import run.halo.app.theme.router.strategy.ModelConst; /** - * The {@link CategoriesPostRouteFactory} for generate {@link RouterFunction} specific to the + * The {@link CategoriesRouteFactory} for generate {@link RouterFunction} specific to the * template * categories.html. * @@ -24,7 +23,7 @@ */ @Component @AllArgsConstructor -public class CategoriesPostRouteFactory implements RouteFactory { +public class CategoriesRouteFactory implements RouteFactory { private final CategoryFinder categoryFinder; diff --git a/src/main/java/run/halo/app/theme/router/poc/CategoryPostRouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/CategoryPostRouteFactory.java similarity index 70% rename from src/main/java/run/halo/app/theme/router/poc/CategoryPostRouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/CategoryPostRouteFactory.java index 3b0b18f3206..871ab9879f2 100644 --- a/src/main/java/run/halo/app/theme/router/poc/CategoryPostRouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/CategoryPostRouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -15,16 +15,18 @@ import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; +import run.halo.app.core.extension.content.Category; +import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.exception.NotFoundException; import run.halo.app.infra.utils.PathUtils; import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.CategoryFinder; import run.halo.app.theme.finders.PostFinder; +import run.halo.app.theme.finders.vo.CategoryVo; import run.halo.app.theme.finders.vo.ListedPostVo; import run.halo.app.theme.router.PageUrlUtils; import run.halo.app.theme.router.UrlContextListResult; import run.halo.app.theme.router.ViewNameResolver; -import run.halo.app.theme.router.strategy.ModelConst; /** * The {@link CategoryPostRouteFactory} for generate {@link RouterFunction} specific to the template @@ -40,35 +42,46 @@ public class CategoryPostRouteFactory implements RouteFactory { private final PostFinder postFinder; private final SystemConfigurableEnvironmentFetcher environmentFetcher; - private CategoryFinder categoryFinder; + private final ReactiveExtensionClient client; private final ViewNameResolver viewNameResolver; @Override public RouterFunction create(String prefix) { - return RouterFunctions.route(GET(PathUtils.combinePath(prefix, "/{name}")) - .or(GET(PathUtils.combinePath(prefix, "/{name}/page/{page:\\d+}"))) + return RouterFunctions.route(GET(PathUtils.combinePath(prefix, "/{slug}")) + .or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page:\\d+}"))) .and(accept(MediaType.TEXT_HTML)), handlerFunction()); } HandlerFunction handlerFunction() { return request -> { - Map model = new HashMap<>(); - model.put("posts", postListByCategoryName(request)); - model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.CATEGORY.getValue()); - return categoryFinder.getByName(request.pathVariable("name")) + String slug = request.pathVariable("slug"); + return fetchBySlug(slug) .flatMap(categoryVo -> { + Map model = new HashMap<>(); + model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.CATEGORY.getValue()); + model.put("posts", + postListByCategoryName(categoryVo.getMetadata().getName(), request)); model.put("category", categoryVo); String template = categoryVo.getSpec().getTemplate(); return viewNameResolver.resolveViewNameOrDefault(request, template, DefaultTemplateEnum.CATEGORY.getValue()) .flatMap(viewName -> ServerResponse.ok().render(viewName, model)); - }); + }) + .switchIfEmpty( + Mono.error(new NotFoundException("Category not found with slug: " + slug))); }; } - private Mono> postListByCategoryName(ServerRequest request) { + Mono fetchBySlug(String slug) { + return client.list(Category.class, category -> category.getSpec().getSlug().equals(slug) + && category.getMetadata().getDeletionTimestamp() == null, null) + .next() + .map(CategoryVo::from); + } + + private Mono> postListByCategoryName(String name, + ServerRequest request) { String path = request.path(); - String name = request.pathVariable("name"); int pageNum = pageNumInPathVariable(request); return configuredPageSize(environmentFetcher) .flatMap(pageSize -> postFinder.listByCategory(pageNum, pageSize, name)) diff --git a/src/main/java/run/halo/app/theme/router/poc/IndexRouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/IndexRouteFactory.java similarity index 96% rename from src/main/java/run/halo/app/theme/router/poc/IndexRouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/IndexRouteFactory.java index b5f08cb298c..125d878b2ec 100644 --- a/src/main/java/run/halo/app/theme/router/poc/IndexRouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/IndexRouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -20,7 +20,6 @@ import run.halo.app.theme.finders.vo.ListedPostVo; import run.halo.app.theme.router.PageUrlUtils; import run.halo.app.theme.router.UrlContextListResult; -import run.halo.app.theme.router.strategy.ModelConst; /** * The {@link IndexRouteFactory} for generate {@link RouterFunction} specific to the template diff --git a/src/main/java/run/halo/app/theme/router/strategy/ModelConst.java b/src/main/java/run/halo/app/theme/router/factories/ModelConst.java similarity index 83% rename from src/main/java/run/halo/app/theme/router/strategy/ModelConst.java rename to src/main/java/run/halo/app/theme/router/factories/ModelConst.java index 090a812f491..67985ab0f5d 100644 --- a/src/main/java/run/halo/app/theme/router/strategy/ModelConst.java +++ b/src/main/java/run/halo/app/theme/router/factories/ModelConst.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.strategy; +package run.halo.app.theme.router.factories; /** * Static variable keys for view model. diff --git a/src/main/java/run/halo/app/theme/router/poc/PostRouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/PostRouteFactory.java similarity index 94% rename from src/main/java/run/halo/app/theme/router/poc/PostRouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/PostRouteFactory.java index 776ac3e6f29..6ebce909fad 100644 --- a/src/main/java/run/halo/app/theme/router/poc/PostRouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/PostRouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -28,6 +28,7 @@ import run.halo.app.extension.GVK; import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.infra.exception.NotFoundException; import run.halo.app.infra.utils.JsonUtils; import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.finders.PostFinder; @@ -58,7 +59,7 @@ public RouterFunction create(String pattern) { new PostRequestParamPredicate(pattern); if (postParamPredicate.isQueryParamPattern()) { RequestPredicate requestPredicate = postParamPredicate.requestPredicate(); - return RouterFunctions.route(requestPredicate, handlerFunction()); + return RouterFunctions.route(GET("/").and(requestPredicate), handlerFunction()); } return RouterFunctions .route(GET(pattern).and(accept(MediaType.TEXT_HTML)), handlerFunction()); @@ -81,7 +82,7 @@ HandlerFunction handlerFunction() { DefaultTemplateEnum.POST.getValue()) .flatMap(templateName -> ServerResponse.ok().render(templateName, model)); }) - .switchIfEmpty(ServerResponse.notFound().build()); + .switchIfEmpty(Mono.error(new NotFoundException("Post not found"))); }; } @@ -149,7 +150,7 @@ static Map mergedVariables(ServerRequest request) { } } - class PostRequestParamPredicate { + static class PostRequestParamPredicate { static final String NAME_PARAM = "name"; static final String SLUG_PARAM = "slug"; private final String pattern; @@ -177,13 +178,12 @@ RequestPredicate requestPredicate() { if (NAME_PARAM.equals(placeholderName)) { return RequestPredicates.queryParam(paramName, - name -> client.fetch(Post.class, name).blockOptional().isPresent()); + name -> true); } if (SLUG_PARAM.equals(placeholderName)) { return RequestPredicates.queryParam(paramName, - slug -> client.list(Post.class, post -> post.getSpec().getSlug().equals(slug), - null).next().blockOptional().isPresent()); + slug -> true); } throw new IllegalArgumentException( String.format("Unknown param value placeholder [%s] in pattern [%s]", diff --git a/src/main/java/run/halo/app/theme/router/poc/RouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/RouteFactory.java similarity index 91% rename from src/main/java/run/halo/app/theme/router/poc/RouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/RouteFactory.java index 33ee821812e..94c03a9cb6d 100644 --- a/src/main/java/run/halo/app/theme/router/poc/RouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/RouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; @@ -8,7 +8,6 @@ import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -import run.halo.app.theme.router.strategy.ModelConst; /** * @author guqing diff --git a/src/main/java/run/halo/app/theme/router/poc/SinglePageRoute.java b/src/main/java/run/halo/app/theme/router/factories/SinglePageRoute.java similarity index 56% rename from src/main/java/run/halo/app/theme/router/poc/SinglePageRoute.java rename to src/main/java/run/halo/app/theme/router/factories/SinglePageRoute.java index 981be8fa84b..6396bfce974 100644 --- a/src/main/java/run/halo/app/theme/router/poc/SinglePageRoute.java +++ b/src/main/java/run/halo/app/theme/router/factories/SinglePageRoute.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static java.nio.charset.StandardCharsets.UTF_8; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; @@ -11,7 +11,7 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.DisposableBean; import org.springframework.http.MediaType; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; @@ -24,15 +24,17 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import run.halo.app.core.extension.content.SinglePage; -import run.halo.app.extension.Extension; +import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.ExtensionOperator; import run.halo.app.extension.GVK; import run.halo.app.extension.GroupVersionKind; -import run.halo.app.extension.ReactiveExtensionClient; -import run.halo.app.extension.Watcher; +import run.halo.app.extension.controller.Controller; +import run.halo.app.extension.controller.ControllerBuilder; +import run.halo.app.extension.controller.Reconciler; +import run.halo.app.infra.exception.NotFoundException; import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.finders.SinglePageFinder; import run.halo.app.theme.router.ViewNameResolver; -import run.halo.app.theme.router.strategy.ModelConst; /** * The {@link SinglePageRoute} for route request to specific template page.html. @@ -42,14 +44,14 @@ */ @Component @RequiredArgsConstructor -public class SinglePageRoute implements RouterFunction, - InitializingBean { +public class SinglePageRoute + implements RouterFunction, Reconciler, DisposableBean { private final GroupVersionKind gvk = GroupVersionKind.fromExtension(SinglePage.class); private final Map> quickRouteMap = new ConcurrentHashMap<>(); - private final ReactiveExtensionClient client; + private final ExtensionClient client; private final SinglePageFinder singlePageFinder; @@ -80,11 +82,34 @@ private List> routerFunctions() { } @Override - public void afterPropertiesSet() throws Exception { - client.watch(new SinglePageWatcher()); + public Result reconcile(Request request) { + client.fetch(SinglePage.class, request.name()) + .ifPresent(page -> { + if (ExtensionOperator.isDeleted(page)) { + quickRouteMap.remove(NameSlugPair.from(page)); + return; + } + quickRouteMap.put(NameSlugPair.from(page), handlerFunction(request.name())); + }); + return new Result(false, null); + } + + @Override + public Controller setupWith(ControllerBuilder builder) { + return builder + .extension(new SinglePage()) + .build(); + } + + @Override + public void destroy() throws Exception { + quickRouteMap.clear(); } record NameSlugPair(String name, String slug) { + public static NameSlugPair from(SinglePage page) { + return new NameSlugPair(page.getMetadata().getName(), page.getSpec().getSlug()); + } } String singlePageRoute(String slug) { @@ -92,17 +117,6 @@ String singlePageRoute(String slug) { return StringUtils.prependIfMissing(permalink, "/"); } - private void quickRouteMapUpdater(SinglePageOperation singlePageOperation) { - Operation operation = singlePageOperation.operation(); - NameSlugPair nameSlug = singlePageOperation.nameSlug(); - switch (operation) { - case ADD, UPDATE -> quickRouteMap.put(nameSlug, handlerFunction(nameSlug.name())); - case DELETE -> quickRouteMap.remove(nameSlug); - default -> { - } - } - } - HandlerFunction handlerFunction(String name) { return request -> singlePageFinder.getByName(name) .flatMap(singlePageVo -> { @@ -115,83 +129,14 @@ HandlerFunction handlerFunction(String name) { return viewNameResolver.resolveViewNameOrDefault(request, template, DefaultTemplateEnum.SINGLE_PAGE.getValue()) .flatMap(viewName -> ServerResponse.ok().render(viewName, model)); - }); - } - - record SinglePageOperation(NameSlugPair nameSlug, Operation operation) { - } - - enum Operation { - ADD, UPDATE, DELETE + }) + .switchIfEmpty( + Mono.error(new NotFoundException("Single page not found")) + ); } private String getPlural() { GVK annotation = SinglePage.class.getAnnotation(GVK.class); return annotation.plural(); } - - class SinglePageWatcher implements Watcher { - private boolean disposed = false; - private Runnable disposeHook; - - @Override - public void onAdd(Extension extension) { - if (isNotSinglePage(extension)) { - return; - } - SinglePageOperation op = - createSingleOperation((SinglePage) extension, Operation.ADD); - quickRouteMapUpdater(op); - } - - private SinglePageOperation createSingleOperation(SinglePage page, Operation operation) { - var pair = new NameSlugPair(page.getMetadata().getName(), page.getSpec().getSlug()); - return new SinglePageOperation(pair, operation); - } - - @Override - public void onUpdate(Extension oldExtension, Extension newExtension) { - if (isNotSinglePage(newExtension)) { - return; - } - SinglePageOperation op = - createSingleOperation((SinglePage) newExtension, Operation.UPDATE); - quickRouteMapUpdater(op); - } - - @Override - public void onDelete(Extension extension) { - if (isNotSinglePage(extension)) { - return; - } - SinglePageOperation op = - createSingleOperation((SinglePage) extension, Operation.DELETE); - quickRouteMapUpdater(op); - } - - @Override - public void registerDisposeHook(Runnable dispose) { - this.disposeHook = dispose; - } - - @Override - public void dispose() { - if (isDisposed()) { - return; - } - this.disposed = true; - if (this.disposeHook != null) { - this.disposeHook.run(); - } - } - - @Override - public boolean isDisposed() { - return disposed; - } - - private boolean isNotSinglePage(Extension extension) { - return !(extension instanceof SinglePage); - } - } } diff --git a/src/main/java/run/halo/app/theme/router/poc/TagPostRouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/TagPostRouteFactory.java similarity index 95% rename from src/main/java/run/halo/app/theme/router/poc/TagPostRouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/TagPostRouteFactory.java index be4d8bbf7a5..15d5eb8d76d 100644 --- a/src/main/java/run/halo/app/theme/router/poc/TagPostRouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/TagPostRouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -82,7 +82,8 @@ private Mono tagBySlug(String slug) { && tag.getMetadata().getDeletionTimestamp() == null, null) .next() .flatMap(tag -> tagFinder.getByName(tag.getMetadata().getName())) - .switchIfEmpty(Mono.error(new NotFoundException("Tag not found with slug: " + slug))); + .switchIfEmpty( + Mono.error(new NotFoundException("Tag not found with slug: " + slug))); } } diff --git a/src/main/java/run/halo/app/theme/router/poc/TagsRouteFactory.java b/src/main/java/run/halo/app/theme/router/factories/TagsRouteFactory.java similarity index 94% rename from src/main/java/run/halo/app/theme/router/poc/TagsRouteFactory.java rename to src/main/java/run/halo/app/theme/router/factories/TagsRouteFactory.java index 198376f8486..32b5f43dc15 100644 --- a/src/main/java/run/halo/app/theme/router/poc/TagsRouteFactory.java +++ b/src/main/java/run/halo/app/theme/router/factories/TagsRouteFactory.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.poc; +package run.halo.app.theme.router.factories; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; @@ -14,7 +14,6 @@ import org.springframework.web.reactive.function.server.ServerResponse; import run.halo.app.theme.DefaultTemplateEnum; import run.halo.app.theme.finders.TagFinder; -import run.halo.app.theme.router.strategy.ModelConst; /** * The {@link TagsRouteFactory} for generate {@link RouterFunction} specific to the template 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 deleted file mode 100644 index 42d1822092e..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java +++ /dev/null @@ -1,88 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; -import static run.halo.app.theme.router.PageUrlUtils.pageNum; -import static run.halo.app.theme.router.PageUrlUtils.totalPage; -import static run.halo.app.theme.router.strategy.ModelConst.DEFAULT_PAGE_SIZE; - -import java.util.List; -import java.util.Map; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -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.PostArchiveVo; -import run.halo.app.theme.router.PageUrlUtils; -import run.halo.app.theme.router.UrlContextListResult; - -/** - * The {@link ArchivesRouteStrategy} for generate {@link HandlerFunction} specific to the template - * posts.html. - * - * @author guqing - * @since 2.0.0 - */ -@Component -public class ArchivesRouteStrategy implements ListPageRouteHandlerStrategy { - private final PostFinder postFinder; - private final SystemConfigurableEnvironmentFetcher environmentFetcher; - - public ArchivesRouteStrategy(PostFinder postFinder, - SystemConfigurableEnvironmentFetcher environmentFetcher) { - this.postFinder = postFinder; - this.environmentFetcher = environmentFetcher; - } - - private Mono> postList(ServerRequest request) { - String year = pathVariable(request, "year"); - String month = pathVariable(request, "month"); - String path = request.path(); - return environmentFetcher.fetchPost() - .map(postSetting -> defaultIfNull(postSetting.getArchivePageSize(), DEFAULT_PAGE_SIZE)) - .flatMap(pageSize -> postFinder.archives(pageNum(request), pageSize, year, month)) - .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("archives", postList(request))); - } - - @Override - public List getRouterPaths(String prefix) { - return List.of( - StringUtils.prependIfMissing(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+}") - ); - } - - @Override - public boolean supports(DefaultTemplateEnum template) { - return DefaultTemplateEnum.ARCHIVES.equals(template); - } -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/AuthorRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/AuthorRouteStrategy.java deleted file mode 100644 index 0c243a4997a..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/AuthorRouteStrategy.java +++ /dev/null @@ -1,76 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; -import static run.halo.app.theme.router.PageUrlUtils.pageNum; -import static run.halo.app.theme.router.PageUrlUtils.totalPage; - -import java.util.Map; -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.core.extension.User; -import run.halo.app.extension.GroupVersionKind; -import run.halo.app.extension.ReactiveExtensionClient; -import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -import run.halo.app.infra.SystemSetting; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.PostFinder; -import run.halo.app.theme.finders.vo.ListedPostVo; -import run.halo.app.theme.finders.vo.UserVo; -import run.halo.app.theme.router.PageUrlUtils; -import run.halo.app.theme.router.UrlContextListResult; - -/** - * Author route strategy. - * - * @author guqing - * @since 2.0.1 - */ -@Component -@AllArgsConstructor -public class AuthorRouteStrategy implements DetailsPageRouteHandlerStrategy { - - private final ReactiveExtensionClient client; - - private final PostFinder postFinder; - - private final SystemConfigurableEnvironmentFetcher environmentFetcher; - - @Override - public HandlerFunction getHandler(SystemSetting.ThemeRouteRules routeRules, - String name) { - return request -> ServerResponse.ok() - .render(DefaultTemplateEnum.AUTHOR.getValue(), - Map.of("name", name, - "author", getByName(name), - "posts", postList(request, name), - ModelConst.TEMPLATE_ID, DefaultTemplateEnum.AUTHOR.getValue() - ) - ); - } - - private Mono> postList(ServerRequest request, String name) { - String path = request.path(); - return environmentFetcher.fetchPost() - .map(p -> defaultIfNull(p.getPostPageSize(), ModelConst.DEFAULT_PAGE_SIZE)) - .flatMap(pageSize -> postFinder.listByOwner(pageNum(request), pageSize, name)) - .map(list -> new UrlContextListResult.Builder() - .listResult(list) - .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) - .prevUrl(PageUrlUtils.prevPageUrl(path)) - .build()); - } - - private Mono getByName(String name) { - return client.fetch(User.class, name) - .map(UserVo::from); - } - - @Override - public boolean supports(GroupVersionKind gvk) { - return GroupVersionKind.fromExtension(User.class).equals(gvk); - } -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/CategoriesRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/CategoriesRouteStrategy.java deleted file mode 100644 index 084860ea6fe..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/CategoriesRouteStrategy.java +++ /dev/null @@ -1,42 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import java.util.List; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.CategoryFinder; - -/** - * Categories router strategy for generate {@link HandlerFunction} specific to the template - * categories.html. - * - * @author guqing - * @since 2.0.0 - */ -@Component -@AllArgsConstructor -public class CategoriesRouteStrategy implements ListPageRouteHandlerStrategy { - private final CategoryFinder categoryFinder; - - @Override - public HandlerFunction getHandler() { - return request -> ServerResponse.ok() - .render(DefaultTemplateEnum.CATEGORIES.getValue(), - Map.of("categories", categoryFinder.listAsTree(), - ModelConst.TEMPLATE_ID, DefaultTemplateEnum.CATEGORIES.getValue())); - } - - @Override - public List getRouterPaths(String prefix) { - return List.of(StringUtils.prependIfMissing(prefix, "/")); - } - - @Override - public boolean supports(DefaultTemplateEnum template) { - return DefaultTemplateEnum.CATEGORIES.equals(template); - } -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/CategoryRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/CategoryRouteStrategy.java deleted file mode 100644 index 2bda1ee1312..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/CategoryRouteStrategy.java +++ /dev/null @@ -1,83 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; -import static run.halo.app.theme.router.PageUrlUtils.pageNum; -import static run.halo.app.theme.router.PageUrlUtils.totalPage; - -import java.util.HashMap; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.core.extension.content.Category; -import run.halo.app.extension.GroupVersionKind; -import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -import run.halo.app.infra.SystemSetting; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.CategoryFinder; -import run.halo.app.theme.finders.PostFinder; -import run.halo.app.theme.finders.vo.ListedPostVo; -import run.halo.app.theme.router.PageUrlUtils; -import run.halo.app.theme.router.UrlContextListResult; -import run.halo.app.theme.router.ViewNameResolver; - -/** - * The {@link CategoryRouteStrategy} for generate {@link HandlerFunction} specific to the template - * category.html. - * - * @author guqing - * @since 2.0.0 - */ -@Component -@AllArgsConstructor -public class CategoryRouteStrategy implements DetailsPageRouteHandlerStrategy { - private final GroupVersionKind gvk = GroupVersionKind.fromExtension(Category.class); - private final PostFinder postFinder; - - private final CategoryFinder categoryFinder; - - private final ViewNameResolver viewNameResolver; - - private final SystemConfigurableEnvironmentFetcher environmentFetcher; - - private Mono> postListByCategoryName(String name, - ServerRequest request) { - String path = request.path(); - return environmentFetcher.fetchPost() - .map(post -> defaultIfNull(post.getCategoryPageSize(), ModelConst.DEFAULT_PAGE_SIZE)) - .flatMap( - pageSize -> postFinder.listByCategory(pageNum(request), pageSize, name)) - .map(list -> new UrlContextListResult.Builder() - .listResult(list) - .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) - .prevUrl(PageUrlUtils.prevPageUrl(path)) - .build()); - } - - @Override - public HandlerFunction getHandler(SystemSetting.ThemeRouteRules routeRules, - String name) { - return request -> { - Map model = new HashMap<>(); - model.put("name", name); - model.put("posts", postListByCategoryName(name, request)); - - model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.CATEGORY.getValue()); - return categoryFinder.getByName(name).flatMap(categoryVo -> { - model.put("category", categoryVo); - String template = categoryVo.getSpec().getTemplate(); - return viewNameResolver.resolveViewNameOrDefault(request, template, - DefaultTemplateEnum.CATEGORY.getValue()) - .flatMap(viewName -> ServerResponse.ok().render(viewName, model)); - }); - }; - } - - @Override - public boolean supports(GroupVersionKind gvk) { - return this.gvk.equals(gvk); - } -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/DetailsPageRouteHandlerStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/DetailsPageRouteHandlerStrategy.java deleted file mode 100644 index ba4bd692b97..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/DetailsPageRouteHandlerStrategy.java +++ /dev/null @@ -1,21 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.extension.GroupVersionKind; -import run.halo.app.infra.SystemSetting; - -/** - * The {@link DetailsPageRouteHandlerStrategy} for generate {@link HandlerFunction} specific to the - * template. - * - * @author guqing - * @since 2.0.0 - */ -public interface DetailsPageRouteHandlerStrategy { - - HandlerFunction getHandler(SystemSetting.ThemeRouteRules routeRules, - String name); - - boolean supports(GroupVersionKind gvk); -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/IndexRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/IndexRouteStrategy.java deleted file mode 100644 index c89d01e676a..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/IndexRouteStrategy.java +++ /dev/null @@ -1,66 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; -import static run.halo.app.theme.router.PageUrlUtils.pageNum; -import static run.halo.app.theme.router.PageUrlUtils.totalPage; -import static run.halo.app.theme.router.strategy.ModelConst.DEFAULT_PAGE_SIZE; - -import java.util.List; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.PostFinder; -import run.halo.app.theme.finders.vo.ListedPostVo; -import run.halo.app.theme.router.PageUrlUtils; -import run.halo.app.theme.router.UrlContextListResult; - -/** - * The {@link IndexRouteStrategy} for generate {@link HandlerFunction} specific to the template - * index.html. - * - * @author guqing - * @since 2.0.0 - */ -@Component -@AllArgsConstructor -public class IndexRouteStrategy implements ListPageRouteHandlerStrategy { - - private final PostFinder postFinder; - private final SystemConfigurableEnvironmentFetcher environmentFetcher; - - private Mono> postList(ServerRequest request) { - String path = request.path(); - return environmentFetcher.fetchPost() - .map(p -> defaultIfNull(p.getPostPageSize(), DEFAULT_PAGE_SIZE)) - .flatMap(pageSize -> postFinder.list(pageNum(request), pageSize)) - .map(list -> new UrlContextListResult.Builder() - .listResult(list) - .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) - .prevUrl(PageUrlUtils.prevPageUrl(path)) - .build()); - } - - @Override - public HandlerFunction getHandler() { - return request -> ServerResponse.ok() - .render(DefaultTemplateEnum.INDEX.getValue(), - Map.of("posts", postList(request), - ModelConst.TEMPLATE_ID, DefaultTemplateEnum.INDEX.getValue())); - } - - @Override - public List getRouterPaths(String pattern) { - return List.of("/", "/index"); - } - - @Override - public boolean supports(DefaultTemplateEnum template) { - return DefaultTemplateEnum.INDEX.equals(template); - } -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/ListPageRouteHandlerStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/ListPageRouteHandlerStrategy.java deleted file mode 100644 index 96a6f18a0ab..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/ListPageRouteHandlerStrategy.java +++ /dev/null @@ -1,22 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import java.util.List; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.theme.DefaultTemplateEnum; - -/** - * The {@link ListPageRouteHandlerStrategy} for generate {@link HandlerFunction} specific to the - * template. - * - * @author guqing - * @since 2.0.0 - */ -public interface ListPageRouteHandlerStrategy { - - HandlerFunction getHandler(); - - List getRouterPaths(String pattern); - - boolean supports(DefaultTemplateEnum template); -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/PostRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/PostRouteStrategy.java deleted file mode 100644 index 5440f4a0631..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/PostRouteStrategy.java +++ /dev/null @@ -1,72 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import java.util.HashMap; -import java.util.Map; -import org.springframework.http.server.PathContainer; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import org.springframework.web.util.pattern.PathPattern; -import org.springframework.web.util.pattern.PathPatternParser; -import run.halo.app.core.extension.content.Post; -import run.halo.app.extension.GVK; -import run.halo.app.extension.GroupVersionKind; -import run.halo.app.infra.SystemSetting; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.PostFinder; -import run.halo.app.theme.router.ViewNameResolver; - -/** - * The {@link PostRouteStrategy} for generate {@link HandlerFunction} specific to the template - * post.html. - * - * @author guqing - * @since 2.0.0 - */ -@Component -public class PostRouteStrategy implements DetailsPageRouteHandlerStrategy { - static final String NAME_PARAM = "name"; - private final GroupVersionKind groupVersionKind = GroupVersionKind.fromExtension(Post.class); - private final PostFinder postFinder; - private final ViewNameResolver viewNameResolver; - - public PostRouteStrategy(PostFinder postFinder, ViewNameResolver viewNameResolver) { - this.postFinder = postFinder; - this.viewNameResolver = viewNameResolver; - } - - @Override - public HandlerFunction getHandler(SystemSetting.ThemeRouteRules routeRules, - final String name) { - return request -> { - String pattern = routeRules.getPost(); - final GVK gvk = Post.class.getAnnotation(GVK.class); - PathPattern parse = PathPatternParser.defaultInstance.parse(pattern); - PathPattern.PathMatchInfo pathMatchInfo = - parse.matchAndExtract(PathContainer.parsePath(request.path())); - Map model = new HashMap<>(); - model.put(NAME_PARAM, name); - if (pathMatchInfo != null) { - model.putAll(pathMatchInfo.getUriVariables()); - } - // used by HaloTrackerProcessor - model.put("groupVersionKind", groupVersionKind); - model.put("plural", gvk.plural()); - // used by TemplateGlobalHeadProcessor and PostTemplateHeadProcessor - model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.POST.getValue()); - return postFinder.getByName(name) - .flatMap(postVo -> { - model.put("post", postVo); - String template = postVo.getSpec().getTemplate(); - return viewNameResolver.resolveViewNameOrDefault(request, template, - DefaultTemplateEnum.POST.getValue()) - .flatMap(templateName -> ServerResponse.ok().render(templateName, model)); - }); - }; - } - - @Override - public boolean supports(GroupVersionKind gvk) { - return groupVersionKind.equals(gvk); - } -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategy.java deleted file mode 100644 index 3085e789f96..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategy.java +++ /dev/null @@ -1,64 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import java.util.HashMap; -import java.util.Map; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.core.extension.content.SinglePage; -import run.halo.app.extension.GVK; -import run.halo.app.extension.GroupVersionKind; -import run.halo.app.infra.SystemSetting; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.SinglePageFinder; -import run.halo.app.theme.router.ViewNameResolver; - -/** - * The {@link SinglePageRouteStrategy} for generate {@link HandlerFunction} specific to the template - * page.html. - * - * @author guqing - * @since 2.0.0 - */ -@Component -public class SinglePageRouteStrategy implements DetailsPageRouteHandlerStrategy { - private final GroupVersionKind gvk = GroupVersionKind.fromExtension(SinglePage.class); - private final SinglePageFinder singlePageFinder; - private final ViewNameResolver viewNameResolver; - - public SinglePageRouteStrategy(SinglePageFinder singlePageFinder, - ViewNameResolver viewNameResolver) { - this.singlePageFinder = singlePageFinder; - this.viewNameResolver = viewNameResolver; - } - - private String getPlural() { - GVK annotation = SinglePage.class.getAnnotation(GVK.class); - return annotation.plural(); - } - - @Override - public HandlerFunction getHandler(SystemSetting.ThemeRouteRules routeRules, - String name) { - return request -> { - Map model = new HashMap<>(); - model.put("name", name); - model.put("groupVersionKind", gvk); - model.put("plural", getPlural()); - model.put(ModelConst.TEMPLATE_ID, DefaultTemplateEnum.SINGLE_PAGE.getValue()); - - return singlePageFinder.getByName(name).flatMap(singlePageVo -> { - model.put("singlePage", singlePageVo); - String template = singlePageVo.getSpec().getTemplate(); - return viewNameResolver.resolveViewNameOrDefault(request, template, - DefaultTemplateEnum.SINGLE_PAGE.getValue()) - .flatMap(viewName -> ServerResponse.ok().render(viewName, model)); - }); - }; - } - - @Override - public boolean supports(GroupVersionKind gvk) { - return this.gvk.equals(gvk); - } -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/TagRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/TagRouteStrategy.java deleted file mode 100644 index c0572ceef00..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/TagRouteStrategy.java +++ /dev/null @@ -1,72 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; -import static run.halo.app.theme.router.PageUrlUtils.pageNum; -import static run.halo.app.theme.router.PageUrlUtils.totalPage; - -import java.util.Map; -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.core.extension.content.Tag; -import run.halo.app.extension.GroupVersionKind; -import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -import run.halo.app.infra.SystemSetting; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.PostFinder; -import run.halo.app.theme.finders.TagFinder; -import run.halo.app.theme.finders.vo.ListedPostVo; -import run.halo.app.theme.router.PageUrlUtils; -import run.halo.app.theme.router.UrlContextListResult; - -/** - * The {@link TagRouteStrategy} for generate {@link RouterFunction} specific to the template - * tag.html. - * - * @author guqing - * @since 2.0.0 - */ -@Component -@AllArgsConstructor -public class TagRouteStrategy implements DetailsPageRouteHandlerStrategy { - private final GroupVersionKind gvk = GroupVersionKind.fromExtension(Tag.class); - private final PostFinder postFinder; - - private final TagFinder tagFinder; - - private final SystemConfigurableEnvironmentFetcher environmentFetcher; - - private Mono> postList(ServerRequest request, String name) { - String path = request.path(); - return environmentFetcher.fetchPost() - .map(p -> defaultIfNull(p.getTagPageSize(), ModelConst.DEFAULT_PAGE_SIZE)) - .flatMap(pageSize -> postFinder.listByTag(pageNum(request), pageSize, name)) - .map(list -> new UrlContextListResult.Builder() - .listResult(list) - .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) - .prevUrl(PageUrlUtils.prevPageUrl(path)) - .build()); - } - - @Override - public HandlerFunction getHandler(SystemSetting.ThemeRouteRules routeRules, - String name) { - return request -> ServerResponse.ok() - .render(DefaultTemplateEnum.TAG.getValue(), - Map.of("name", name, - "posts", postList(request, name), - "tag", tagFinder.getByName(name), - ModelConst.TEMPLATE_ID, DefaultTemplateEnum.TAG.getValue() - ) - ); - } - - @Override - public boolean supports(GroupVersionKind gvk) { - return this.gvk.equals(gvk); - } -} diff --git a/src/main/java/run/halo/app/theme/router/strategy/TagsRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/TagsRouteStrategy.java deleted file mode 100644 index d669a238be8..00000000000 --- a/src/main/java/run/halo/app/theme/router/strategy/TagsRouteStrategy.java +++ /dev/null @@ -1,47 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import java.util.List; -import java.util.Map; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.finders.TagFinder; - -/** - * The {@link TagsRouteStrategy} for generate {@link HandlerFunction} specific to the template - * tags.html. - * - * @author guqing - * @since 2.0.0 - */ -@Component -public class TagsRouteStrategy implements ListPageRouteHandlerStrategy { - - private final TagFinder tagFinder; - - public TagsRouteStrategy(TagFinder tagFinder) { - this.tagFinder = tagFinder; - } - - @Override - public HandlerFunction getHandler() { - return request -> ServerResponse.ok() - .render(DefaultTemplateEnum.TAGS.getValue(), - Map.of("tags", tagFinder.listAll(), - ModelConst.TEMPLATE_ID, DefaultTemplateEnum.TAGS.getValue() - ) - ); - } - - @Override - public List getRouterPaths(String prefix) { - return List.of(StringUtils.prependIfMissing(prefix, "/")); - } - - @Override - public boolean supports(DefaultTemplateEnum template) { - return DefaultTemplateEnum.TAGS.equals(template); - } -} diff --git a/src/test/java/run/halo/app/content/permalinks/CategoryPermalinkPolicyTest.java b/src/test/java/run/halo/app/content/permalinks/CategoryPermalinkPolicyTest.java index 645f90629e0..fbd1e1ac708 100644 --- a/src/test/java/run/halo/app/content/permalinks/CategoryPermalinkPolicyTest.java +++ b/src/test/java/run/halo/app/content/permalinks/CategoryPermalinkPolicyTest.java @@ -1,7 +1,6 @@ package run.halo.app.content.permalinks; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import java.net.URI; @@ -10,12 +9,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.context.ApplicationContext; import run.halo.app.core.extension.content.Category; import run.halo.app.extension.Metadata; import run.halo.app.infra.ExternalUrlSupplier; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.router.PermalinkPatternProvider; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; /** * Tests for {@link CategoryPermalinkPolicy}. @@ -27,27 +24,21 @@ class CategoryPermalinkPolicyTest { @Mock - private PermalinkPatternProvider permalinkPatternProvider; + private ExternalUrlSupplier externalUrlSupplier; @Mock - private ApplicationContext applicationContext; - - @Mock - private ExternalUrlSupplier externalUrlSupplier; + private SystemConfigurableEnvironmentFetcher environmentFetcher; private CategoryPermalinkPolicy categoryPermalinkPolicy; @BeforeEach void setUp() { categoryPermalinkPolicy = - new CategoryPermalinkPolicy(applicationContext, permalinkPatternProvider, - externalUrlSupplier); + new CategoryPermalinkPolicy(externalUrlSupplier, environmentFetcher); } @Test void permalink() { - when(permalinkPatternProvider.getPattern(eq(DefaultTemplateEnum.CATEGORY))) - .thenReturn("categories"); Category category = new Category(); Metadata metadata = new Metadata(); metadata.setName("category-test"); @@ -70,18 +61,4 @@ void permalink() { permalink = categoryPermalinkPolicy.permalink(category); assertThat(permalink).isEqualTo("http://exmaple.com/categories/%E4%B8%AD%E6%96%87%20slug"); } - - @Test - void templateName() { - String s = categoryPermalinkPolicy.templateName(); - assertThat(s).isEqualTo(DefaultTemplateEnum.CATEGORY.getValue()); - } - - @Test - void pattern() { - when(permalinkPatternProvider.getPattern(eq(DefaultTemplateEnum.CATEGORY))) - .thenReturn("categories"); - String pattern = categoryPermalinkPolicy.pattern(); - assertThat(pattern).isEqualTo("categories"); - } } \ No newline at end of file diff --git a/src/test/java/run/halo/app/content/permalinks/PostPermalinkPolicyTest.java b/src/test/java/run/halo/app/content/permalinks/PostPermalinkPolicyTest.java index 43cd41173c1..d3ca4cef9a8 100644 --- a/src/test/java/run/halo/app/content/permalinks/PostPermalinkPolicyTest.java +++ b/src/test/java/run/halo/app/content/permalinks/PostPermalinkPolicyTest.java @@ -10,6 +10,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,11 +18,12 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.context.ApplicationContext; import run.halo.app.content.TestPost; +import run.halo.app.core.extension.content.Constant; import run.halo.app.core.extension.content.Post; +import run.halo.app.extension.ExtensionUtil; import run.halo.app.infra.ExternalUrlSupplier; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.infra.utils.PathUtils; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.router.PermalinkPatternProvider; /** * Tests for {@link PostPermalinkPolicy}. @@ -33,27 +35,28 @@ class PostPermalinkPolicyTest { private static final NumberFormat NUMBER_FORMAT = new DecimalFormat("00"); - @Mock - private PermalinkPatternProvider permalinkPatternProvider; - @Mock private ApplicationContext applicationContext; @Mock private ExternalUrlSupplier externalUrlSupplier; + @Mock + private SystemConfigurableEnvironmentFetcher environmentFetcher; + private PostPermalinkPolicy postPermalinkPolicy; @BeforeEach void setUp() { lenient().when(externalUrlSupplier.get()).thenReturn(URI.create("")); - postPermalinkPolicy = new PostPermalinkPolicy(permalinkPatternProvider, applicationContext, - externalUrlSupplier); + postPermalinkPolicy = new PostPermalinkPolicy(environmentFetcher, externalUrlSupplier); } @Test void permalink() { Post post = TestPost.postV1(); + Map annotations = ExtensionUtil.nullSafeAnnotations(post); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, "/{year}/{month}/{day}/{slug}"); post.getMetadata().setName("test-post"); post.getSpec().setSlug("test-post-slug"); Instant now = Instant.now(); @@ -64,35 +67,28 @@ void permalink() { String month = NUMBER_FORMAT.format(zonedDateTime.getMonthValue()); String day = NUMBER_FORMAT.format(zonedDateTime.getDayOfMonth()); - // pattern /{year}/{month}/{day}/{slug} - when(permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST)) - .thenReturn("/{year}/{month}/{day}/{slug}"); String permalink = postPermalinkPolicy.permalink(post); assertThat(permalink) .isEqualTo(PathUtils.combinePath(year, month, day, post.getSpec().getSlug())); // pattern {month}/{day}/{slug} - when(permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST)) - .thenReturn("/{month}/{day}/{slug}"); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, "/{month}/{day}/{slug}"); permalink = postPermalinkPolicy.permalink(post); assertThat(permalink) .isEqualTo(PathUtils.combinePath(month, day, post.getSpec().getSlug())); // pattern /?p={name} - when(permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST)) - .thenReturn("/?p={name}"); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, "/?p={name}"); permalink = postPermalinkPolicy.permalink(post); assertThat(permalink).isEqualTo("/?p=test-post"); // pattern /posts/{slug} - when(permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST)) - .thenReturn("/posts/{slug}"); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, "/posts/{slug}"); permalink = postPermalinkPolicy.permalink(post); assertThat(permalink).isEqualTo("/posts/test-post-slug"); // pattern /posts/{name} - when(permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST)) - .thenReturn("/posts/{name}"); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, "/posts/{name}"); permalink = postPermalinkPolicy.permalink(post); assertThat(permalink).isEqualTo("/posts/test-post"); } @@ -100,6 +96,8 @@ void permalink() { @Test void permalinkWithExternalUrl() { Post post = TestPost.postV1(); + Map annotations = ExtensionUtil.nullSafeAnnotations(post); + annotations.put(Constant.PERMALINK_PATTERN_ANNO, "/{year}/{month}/{day}/{slug}"); post.getMetadata().setName("test-post"); post.getSpec().setSlug("test-post-slug"); Instant now = Instant.parse("2022-11-01T02:40:06.806310Z"); @@ -107,8 +105,6 @@ void permalinkWithExternalUrl() { when(externalUrlSupplier.get()).thenReturn(URI.create("http://example.com")); - when(permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST)) - .thenReturn("/{year}/{month}/{day}/{slug}"); String permalink = postPermalinkPolicy.permalink(post); assertThat(permalink).isEqualTo("http://example.com/2022/11/01/test-post-slug"); @@ -116,17 +112,4 @@ void permalinkWithExternalUrl() { permalink = postPermalinkPolicy.permalink(post); assertThat(permalink).isEqualTo("http://example.com/2022/11/01/%E4%B8%AD%E6%96%87%20slug"); } - - @Test - void templateName() { - String s = postPermalinkPolicy.templateName(); - assertThat(s).isEqualTo(DefaultTemplateEnum.POST.getValue()); - } - - @Test - void pattern() { - when(permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST)) - .thenReturn("/{year}/{month}/{day}/{slug}"); - assertThat(postPermalinkPolicy.pattern()).isEqualTo("/{year}/{month}/{day}/{slug}"); - } } \ No newline at end of file diff --git a/src/test/java/run/halo/app/content/permalinks/TagPermalinkPolicyTest.java b/src/test/java/run/halo/app/content/permalinks/TagPermalinkPolicyTest.java index c31751b0df8..cd40a75de44 100644 --- a/src/test/java/run/halo/app/content/permalinks/TagPermalinkPolicyTest.java +++ b/src/test/java/run/halo/app/content/permalinks/TagPermalinkPolicyTest.java @@ -1,7 +1,6 @@ package run.halo.app.content.permalinks; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import java.net.URI; @@ -14,8 +13,7 @@ import run.halo.app.core.extension.content.Tag; import run.halo.app.extension.Metadata; import run.halo.app.infra.ExternalUrlSupplier; -import run.halo.app.theme.DefaultTemplateEnum; -import run.halo.app.theme.router.PermalinkPatternProvider; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; /** * Tests for {@link TagPermalinkPolicy}. @@ -26,28 +24,24 @@ @ExtendWith(MockitoExtension.class) class TagPermalinkPolicyTest { - @Mock - private PermalinkPatternProvider permalinkPatternProvider; - @Mock private ApplicationContext applicationContext; @Mock private ExternalUrlSupplier externalUrlSupplier; + @Mock + private SystemConfigurableEnvironmentFetcher environmentFetcher; + private TagPermalinkPolicy tagPermalinkPolicy; @BeforeEach void setUp() { - tagPermalinkPolicy = new TagPermalinkPolicy(permalinkPatternProvider, applicationContext, - externalUrlSupplier); + tagPermalinkPolicy = new TagPermalinkPolicy(externalUrlSupplier, environmentFetcher); } @Test void permalink() { - when(permalinkPatternProvider.getPattern(eq(DefaultTemplateEnum.TAG))) - .thenReturn("tags"); - Tag tag = new Tag(); Metadata metadata = new Metadata(); metadata.setName("test-tag"); @@ -70,17 +64,4 @@ void permalink() { permalink = tagPermalinkPolicy.permalink(tag); assertThat(permalink).isEqualTo("http://example.com/tags/%E4%B8%AD%E6%96%87slug"); } - - @Test - void templateName() { - String s = tagPermalinkPolicy.templateName(); - assertThat(s).isEqualTo(DefaultTemplateEnum.TAG.getValue()); - } - - @Test - void pattern() { - when(permalinkPatternProvider.getPattern(eq(DefaultTemplateEnum.TAG))) - .thenReturn("tags"); - assertThat(tagPermalinkPolicy.pattern()).isEqualTo("tags"); - } } \ No newline at end of file diff --git a/src/test/java/run/halo/app/core/extension/reconciler/CategoryReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/CategoryReconcilerTest.java index ad4321d59db..edfd2854d86 100644 --- a/src/test/java/run/halo/app/core/extension/reconciler/CategoryReconcilerTest.java +++ b/src/test/java/run/halo/app/core/extension/reconciler/CategoryReconcilerTest.java @@ -47,7 +47,7 @@ void reconcileStatusPostForCategoryA() throws JSONException { reconcileStatusPostPilling("category-A"); ArgumentCaptor captor = ArgumentCaptor.forClass(Category.class); - verify(client, times(2)).update(captor.capture()); + verify(client, times(3)).update(captor.capture()); assertThat(captor.getAllValues().get(1).getStatusOrDefault().getPostCount()).isEqualTo(4); assertThat( captor.getAllValues().get(1).getStatusOrDefault().getVisiblePostCount()).isEqualTo(0); @@ -57,7 +57,7 @@ void reconcileStatusPostForCategoryA() throws JSONException { void reconcileStatusPostForCategoryB() throws JSONException { reconcileStatusPostPilling("category-B"); ArgumentCaptor captor = ArgumentCaptor.forClass(Category.class); - verify(client, times(2)).update(captor.capture()); + verify(client, times(3)).update(captor.capture()); Category category = captor.getAllValues().get(1); assertThat(category.getStatusOrDefault().getPostCount()).isEqualTo(3); assertThat(category.getStatusOrDefault().getVisiblePostCount()).isEqualTo(0); @@ -67,7 +67,7 @@ void reconcileStatusPostForCategoryB() throws JSONException { void reconcileStatusPostForCategoryC() throws JSONException { reconcileStatusPostPilling("category-C"); ArgumentCaptor captor = ArgumentCaptor.forClass(Category.class); - verify(client, times(2)).update(captor.capture()); + verify(client, times(3)).update(captor.capture()); assertThat(captor.getAllValues().get(1).getStatusOrDefault().getPostCount()).isEqualTo(2); assertThat( captor.getAllValues().get(1).getStatusOrDefault().getVisiblePostCount()).isEqualTo(0); @@ -77,7 +77,7 @@ void reconcileStatusPostForCategoryC() throws JSONException { void reconcileStatusPostForCategoryD() throws JSONException { reconcileStatusPostPilling("category-D"); ArgumentCaptor captor = ArgumentCaptor.forClass(Category.class); - verify(client, times(2)).update(captor.capture()); + verify(client, times(3)).update(captor.capture()); assertThat(captor.getAllValues().get(1).getStatusOrDefault().postCount).isEqualTo(1); assertThat(captor.getAllValues().get(1).getStatusOrDefault().visiblePostCount).isEqualTo(0); } diff --git a/src/test/java/run/halo/app/core/extension/reconciler/PostReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/PostReconcilerTest.java index c5e13bf4ca3..01e0ef3f43e 100644 --- a/src/test/java/run/halo/app/core/extension/reconciler/PostReconcilerTest.java +++ b/src/test/java/run/halo/app/core/extension/reconciler/PostReconcilerTest.java @@ -80,9 +80,6 @@ void reconcile() { verify(client, times(3)).update(captor.capture()); verify(postPermalinkPolicy, times(1)).permalink(any()); - verify(postPermalinkPolicy, times(1)).onPermalinkAdd(any()); - verify(postPermalinkPolicy, times(1)).onPermalinkDelete(any()); - verify(postPermalinkPolicy, times(0)).onPermalinkUpdate(any()); Post value = captor.getValue(); assertThat(value.getStatus().getExcerpt()).isNull(); diff --git a/src/test/java/run/halo/app/core/extension/reconciler/SinglePageReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/SinglePageReconcilerTest.java index 6d58cc73ac2..68258e017af 100644 --- a/src/test/java/run/halo/app/core/extension/reconciler/SinglePageReconcilerTest.java +++ b/src/test/java/run/halo/app/core/extension/reconciler/SinglePageReconcilerTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,9 +33,6 @@ import run.halo.app.extension.controller.Reconciler; import run.halo.app.infra.ExternalUrlSupplier; import run.halo.app.metrics.CounterService; -import run.halo.app.theme.router.PermalinkIndexAddCommand; -import run.halo.app.theme.router.PermalinkIndexDeleteCommand; -import run.halo.app.theme.router.PermalinkIndexUpdateCommand; /** * Tests for {@link SinglePageReconciler}. @@ -97,10 +93,6 @@ void reconcile() { SinglePage value = captor.getValue(); assertThat(value.getStatus().getExcerpt()).isEqualTo("hello world"); assertThat(value.getStatus().getContributors()).isEqualTo(List.of("guqing", "zhangsan")); - - verify(applicationContext, times(0)).publishEvent(isA(PermalinkIndexAddCommand.class)); - verify(applicationContext, times(1)).publishEvent(isA(PermalinkIndexDeleteCommand.class)); - verify(applicationContext, times(0)).publishEvent(isA(PermalinkIndexUpdateCommand.class)); } @Test diff --git a/src/test/java/run/halo/app/core/extension/reconciler/TagReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/TagReconcilerTest.java index 1a2cfec0bfa..cf35d484da8 100644 --- a/src/test/java/run/halo/app/core/extension/reconciler/TagReconcilerTest.java +++ b/src/test/java/run/halo/app/core/extension/reconciler/TagReconcilerTest.java @@ -52,17 +52,13 @@ void reconcile() { tagReconciler.reconcile(new TagReconciler.Request("fake-tag")); verify(client, times(3)).update(captor.capture()); - verify(tagPermalinkPolicy, times(1)).onPermalinkAdd(any()); - verify(tagPermalinkPolicy, times(1)).onPermalinkDelete(any()); Tag capture = captor.getValue(); assertThat(capture.getStatus().getPermalink()).isEqualTo("/tags/fake-slug"); // change slug tag.getSpec().setSlug("new-slug"); tagReconciler.reconcile(new TagReconciler.Request("fake-tag")); - verify(client, times(4)).update(captor.capture()); - verify(tagPermalinkPolicy, times(2)).onPermalinkAdd(any()); - verify(tagPermalinkPolicy, times(2)).onPermalinkDelete(any()); + verify(client, times(5)).update(captor.capture()); assertThat(capture.getStatus().getPermalink()).isEqualTo("/tags/new-slug"); } @@ -76,8 +72,6 @@ void reconcileDelete() { tagReconciler.reconcile(new TagReconciler.Request("fake-tag")); verify(client, times(1)).update(captor.capture()); - verify(tagPermalinkPolicy, times(0)).onPermalinkAdd(any()); - verify(tagPermalinkPolicy, times(1)).onPermalinkDelete(any()); verify(tagPermalinkPolicy, times(0)).permalink(any()); } @@ -90,7 +84,7 @@ void reconcileStatusPosts() { ArgumentCaptor captor = ArgumentCaptor.forClass(Tag.class); tagReconciler.reconcile(new TagReconciler.Request("fake-tag")); - verify(client, times(2)).update(captor.capture()); + verify(client, times(3)).update(captor.capture()); List allValues = captor.getAllValues(); assertThat(allValues.get(1).getStatusOrDefault().getPostCount()).isEqualTo(2); assertThat(allValues.get(1).getStatusOrDefault().getVisiblePostCount()).isEqualTo(0); diff --git a/src/test/java/run/halo/app/core/extension/reconciler/UserReconcilerTest.java b/src/test/java/run/halo/app/core/extension/reconciler/UserReconcilerTest.java index fc11fbe4694..57d58f5ffa0 100644 --- a/src/test/java/run/halo/app/core/extension/reconciler/UserReconcilerTest.java +++ b/src/test/java/run/halo/app/core/extension/reconciler/UserReconcilerTest.java @@ -24,7 +24,6 @@ import run.halo.app.extension.controller.Reconciler; import run.halo.app.infra.AnonymousUserConst; import run.halo.app.infra.ExternalUrlSupplier; -import run.halo.app.theme.router.PermalinkIndexUpdateCommand; /** * Tests for {@link UserReconciler}. @@ -54,7 +53,6 @@ void permalinkForFakeUser() throws URISyntaxException { .thenReturn(Optional.of(user("fake-user"))); userReconciler.reconcile(new Reconciler.Request("fake-user")); verify(client, times(1)).update(any(User.class)); - verify(eventPublisher, times(1)).publishEvent(any(PermalinkIndexUpdateCommand.class)); ArgumentCaptor captor = ArgumentCaptor.forClass(User.class); verify(client, times(1)).update(captor.capture()); @@ -68,7 +66,6 @@ void permalinkForAnonymousUser() { .thenReturn(Optional.of(user(AnonymousUserConst.PRINCIPAL))); userReconciler.reconcile(new Reconciler.Request(AnonymousUserConst.PRINCIPAL)); verify(client, times(0)).update(any(User.class)); - verify(eventPublisher, times(0)).publishEvent(any(PermalinkIndexUpdateCommand.class)); } User user(String name) { diff --git a/src/test/java/run/halo/app/theme/dialect/HaloProcessorDialectTest.java b/src/test/java/run/halo/app/theme/dialect/HaloProcessorDialectTest.java index adc1c04d003..30ea6127551 100644 --- a/src/test/java/run/halo/app/theme/dialect/HaloProcessorDialectTest.java +++ b/src/test/java/run/halo/app/theme/dialect/HaloProcessorDialectTest.java @@ -38,7 +38,7 @@ import run.halo.app.theme.finders.SinglePageFinder; import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.finders.vo.UserVo; -import run.halo.app.theme.router.strategy.ModelConst; +import run.halo.app.theme.router.factories.ModelConst; /** * Tests for {@link HaloProcessorDialect}. diff --git a/src/test/java/run/halo/app/theme/finders/impl/SinglePageFinderImplTest.java b/src/test/java/run/halo/app/theme/finders/impl/SinglePageFinderImplTest.java index 3ad3a3e0193..daab2ca4042 100644 --- a/src/test/java/run/halo/app/theme/finders/impl/SinglePageFinderImplTest.java +++ b/src/test/java/run/halo/app/theme/finders/impl/SinglePageFinderImplTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -15,6 +16,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import run.halo.app.content.SinglePageService; +import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.SinglePage; import run.halo.app.extension.Metadata; import run.halo.app.extension.ReactiveExtensionClient; @@ -52,9 +54,13 @@ void getByName() { SinglePage singlePage = new SinglePage(); singlePage.setMetadata(new Metadata()); singlePage.getMetadata().setName(fakePageName); + singlePage.getMetadata().setLabels(Map.of(SinglePage.PUBLISHED_LABEL, "true")); singlePage.setSpec(new SinglePage.SinglePageSpec()); singlePage.getSpec().setOwner("fake-owner"); singlePage.getSpec().setReleaseSnapshot("fake-release"); + singlePage.getSpec().setPublish(true); + singlePage.getSpec().setDeleted(false); + singlePage.getSpec().setVisible(Post.VisibleEnum.PUBLIC); singlePage.setStatus(new SinglePage.SinglePageStatus()); when(client.fetch(eq(SinglePage.class), eq(fakePageName))) .thenReturn(Mono.just(singlePage)); diff --git a/src/test/java/run/halo/app/theme/router/strategy/EmptyView.java b/src/test/java/run/halo/app/theme/router/EmptyView.java similarity index 92% rename from src/test/java/run/halo/app/theme/router/strategy/EmptyView.java rename to src/test/java/run/halo/app/theme/router/EmptyView.java index 837ffb1fc63..ba4a0bfc2e9 100644 --- a/src/test/java/run/halo/app/theme/router/strategy/EmptyView.java +++ b/src/test/java/run/halo/app/theme/router/EmptyView.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router.strategy; +package run.halo.app.theme.router; import java.util.Map; import org.springframework.http.MediaType; diff --git a/src/test/java/run/halo/app/theme/router/PermalinkIndexerTest.java b/src/test/java/run/halo/app/theme/router/PermalinkIndexerTest.java deleted file mode 100644 index cbe9ed3dcf1..00000000000 --- a/src/test/java/run/halo/app/theme/router/PermalinkIndexerTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package run.halo.app.theme.router; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.util.List; -import java.util.NoSuchElementException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.context.ApplicationContext; -import run.halo.app.content.permalinks.ExtensionLocator; -import run.halo.app.extension.FakeExtension; -import run.halo.app.extension.GroupVersionKind; - -/** - * Tests for {@link PermalinkIndexer}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class PermalinkIndexerTest { - - private final GroupVersionKind gvk = GroupVersionKind.fromExtension(FakeExtension.class); - - private PermalinkIndexer permalinkIndexer; - @Mock - private ApplicationContext applicationContext; - - @BeforeEach - void setUp() { - permalinkIndexer = new PermalinkIndexer(applicationContext); - - ExtensionLocator locator = new ExtensionLocator(gvk, "fake-name", "fake-slug"); - permalinkIndexer.register(locator, "/fake-permalink"); - assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1); - assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1); - } - - @Test - void register() { - ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug"); - permalinkIndexer.register(locator, "/test-permalink"); - verify(applicationContext, times(2)).publishEvent(any(PermalinkIndexChangedEvent.class)); - - assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(2); - assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(2); - } - - @Test - void remove() { - ExtensionLocator locator = new ExtensionLocator(gvk, "fake-name", "fake-slug"); - - assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1); - assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1); - - permalinkIndexer.remove(locator); - assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(0); - assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(0); - - verify(applicationContext, times(2)).publishEvent(any(PermalinkIndexChangedEvent.class)); - } - - @Test - void lookup() { - ExtensionLocator lookup = permalinkIndexer.lookup("/fake-permalink"); - assertThat(lookup).isEqualTo(new ExtensionLocator(gvk, "fake-name", "fake-slug")); - - lookup = permalinkIndexer.lookup("/nothing"); - assertThat(lookup).isNull(); - } - - @Test - void getPermalinks() { - ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug"); - permalinkIndexer.register(locator, "/test-permalink"); - - List permalinks = permalinkIndexer.getPermalinks(gvk); - assertThat(permalinks).isEqualTo(List.of("/fake-permalink", "/test-permalink")); - } - - @Test - void getNames() { - ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug"); - permalinkIndexer.register(locator, "/test-permalink"); - - assertThat(permalinkIndexer.containsName(gvk, "test-name")).isTrue(); - assertThat(permalinkIndexer.containsName(gvk, "nothing")).isFalse(); - } - - @Test - void getSlugs() { - ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug"); - permalinkIndexer.register(locator, "/test-permalink"); - - assertThat(permalinkIndexer.containsSlug(gvk, "fake-slug")).isTrue(); - assertThat(permalinkIndexer.containsSlug(gvk, "test-slug")).isTrue(); - assertThat(permalinkIndexer.containsSlug(gvk, "nothing")).isFalse(); - } - - @Test - void getNameBySlug() { - ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug"); - permalinkIndexer.register(locator, "/test-permalink"); - - String nameBySlug = permalinkIndexer.getNameBySlug(gvk, "test-slug"); - assertThat(nameBySlug).isEqualTo("test-name"); - - nameBySlug = permalinkIndexer.getNameBySlug(gvk, "fake-slug"); - assertThat(nameBySlug).isEqualTo("fake-name"); - - assertThatThrownBy(() -> { - permalinkIndexer.getNameBySlug(gvk, "nothing"); - }).isInstanceOf(NoSuchElementException.class); - } - - @Test - void getNameByPermalink() { - ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug"); - permalinkIndexer.register(locator, "/test-permalink"); - - var name = permalinkIndexer.getNameByPermalink(gvk, "/test-permalink"); - assertEquals("test-name", name); - - name = permalinkIndexer.getNameByPermalink(gvk, "/invalid-permalink"); - assertNull(name); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/PermalinkPatternProviderTest.java b/src/test/java/run/halo/app/theme/router/PermalinkPatternProviderTest.java deleted file mode 100644 index 0cbb6b9e7f1..00000000000 --- a/src/test/java/run/halo/app/theme/router/PermalinkPatternProviderTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package run.halo.app.theme.router; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -import java.util.Map; -import java.util.Optional; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import run.halo.app.extension.ConfigMap; -import run.halo.app.extension.Metadata; -import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; -import run.halo.app.infra.SystemSetting; -import run.halo.app.infra.utils.JsonUtils; -import run.halo.app.theme.DefaultTemplateEnum; - -/** - * Tests for {@link PermalinkPatternProvider}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class PermalinkPatternProviderTest { - - @Mock - private SystemConfigurableEnvironmentFetcher environmentFetcher; - - @InjectMocks - private PermalinkPatternProvider permalinkPatternProvider; - - @Test - void getPatternThenDefault() { - when(environmentFetcher.getConfigMapBlocking()) - .thenReturn(Optional.empty()); - - String pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST); - assertThat(pattern).isEqualTo("/archives/{slug}"); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.TAG); - assertThat(pattern).isEqualTo("tags"); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.TAGS); - assertThat(pattern).isEqualTo("tags"); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.CATEGORY); - assertThat(pattern).isEqualTo("categories"); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.CATEGORIES); - assertThat(pattern).isEqualTo("categories"); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.ARCHIVES); - assertThat(pattern).isEqualTo("archives"); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.INDEX); - assertThat(pattern).isNull(); - } - - @Test - void getPattern() { - ConfigMap configMap = new ConfigMap(); - Metadata metadata = new Metadata(); - metadata.setName("system"); - configMap.setMetadata(metadata); - - SystemSetting.ThemeRouteRules themeRouteRules = new SystemSetting.ThemeRouteRules(); - themeRouteRules.setPost("/posts/{slug}"); - themeRouteRules.setCategories("c"); - themeRouteRules.setTags("t"); - themeRouteRules.setArchives("a"); - - configMap.setData(Map.of("routeRules", JsonUtils.objectToJson(themeRouteRules))); - - when(environmentFetcher.getConfigMapBlocking()) - .thenReturn(Optional.of(configMap)); - - String pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST); - assertThat(pattern).isEqualTo(themeRouteRules.getPost()); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.TAG); - assertThat(pattern).isEqualTo(themeRouteRules.getTags()); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.TAGS); - assertThat(pattern).isEqualTo(themeRouteRules.getTags()); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.CATEGORY); - assertThat(pattern).isEqualTo(themeRouteRules.getCategories()); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.CATEGORIES); - assertThat(pattern).isEqualTo(themeRouteRules.getCategories()); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.ARCHIVES); - assertThat(pattern).isEqualTo(themeRouteRules.getArchives()); - - pattern = permalinkPatternProvider.getPattern(DefaultTemplateEnum.INDEX); - assertThat(pattern).isNull(); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/RadixRouterTreeTest.java b/src/test/java/run/halo/app/theme/router/RadixRouterTreeTest.java deleted file mode 100644 index a13c767e7a1..00000000000 --- a/src/test/java/run/halo/app/theme/router/RadixRouterTreeTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package run.halo.app.theme.router; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.net.URI; -import java.net.URISyntaxException; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.reactive.function.server.MockServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; - -/** - * Tests for {@link RadixRouterTree}. - * - * @author guqing - * @since 2.0.0 - */ -class RadixRouterTreeTest { - - @Test - void pathToFind() throws URISyntaxException { - MockServerRequest request = - MockServerRequest.builder().uri(new URI("/archives")) - .method(HttpMethod.GET).build(); - String path = RadixRouterTree.pathToFind(request); - assertThat(path).isEqualTo("/archives"); - - request = MockServerRequest.builder().uri(new URI("/archives/")) - .method(HttpMethod.GET).build(); - assertThat(RadixRouterTree.pathToFind(request)).isEqualTo("/archives"); - - request = MockServerRequest.builder().uri(new URI("/archives/page/1")) - .method(HttpMethod.GET).build(); - assertThat(RadixRouterTree.pathToFind(request)).isEqualTo("/archives"); - - request = MockServerRequest.builder().uri(new URI("/")) - .method(HttpMethod.GET).build(); - assertThat(RadixRouterTree.pathToFind(request)).isEqualTo("/"); - - request = MockServerRequest.builder().uri(new URI("/")) - .queryParam("p", "fake-post") - .method(HttpMethod.GET).build(); - assertThat(RadixRouterTree.pathToFind(request)).isEqualTo("/?p=fake-post"); - } - - @Test - void shouldInsertKeyWithPercentSign() { - var tree = new RadixRouterTree(); - tree.insert("/1%1", request -> ServerResponse.ok().build()); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/RadixTreeTest.java b/src/test/java/run/halo/app/theme/router/RadixTreeTest.java deleted file mode 100644 index e4bf19f9ae5..00000000000 --- a/src/test/java/run/halo/app/theme/router/RadixTreeTest.java +++ /dev/null @@ -1,267 +0,0 @@ -package run.halo.app.theme.router; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.Test; - -/** - * Tests for {@link RadixTree}. - * - * @author guqing - * @since 2.0.0 - */ -class RadixTreeTest { - - @Test - void insert() { - RadixTree radixTree = new RadixTree<>(); - radixTree.insert("/users", "users"); - radixTree.insert("/users/a", "users-a"); - radixTree.insert("/users/a/b/c", "users-a-b-c"); - radixTree.insert("/users/a/b/c/d", "users-a-b-c-d"); - radixTree.insert("/users/a/b/e/f", "users-a-b-c-d"); - radixTree.insert("/users/b/d", "users-b-d"); - radixTree.insert("/users/b/d/e/f", "users-b-d-e-f"); - radixTree.insert("/users/b/d/g/h", "users-b-d-g-h"); - radixTree.insert("/users/b/f/g/h", "users-b-f-g-h"); - radixTree.insert("/users/c/d/g/h", "users-c-d-g-h"); - radixTree.insert("/users/c/f/g/h", "users-c-f-g-h"); - radixTree.insert("/test/hello", "test-hello"); - radixTree.insert("/test/中文/abc", "test-中文-abc"); - radixTree.insert("/test/中/test", "test-中-test"); - - radixTree.checkIndices(); - String display = radixTree.display(); - assertThat(display).isEqualTo(""" - / [indices=ut] - ├── users [value=users]* - │ └── / [indices=abc] - │ ├── a [value=users-a]* - │ │ └── /b/ [indices=ce] - │ │ ├── c [value=users-a-b-c]* - │ │ │ └── /d [value=users-a-b-c-d]* - │ │ └── e/f [value=users-a-b-c-d]* - │ ├── b/ [indices=df] - │ │ ├── d [value=users-b-d]* - │ │ │ └── / [indices=eg] - │ │ │ ├── e/f [value=users-b-d-e-f]* - │ │ │ └── g/h [value=users-b-d-g-h]* - │ │ └── f/g/h [value=users-b-f-g-h]* - │ └── c/ [indices=df] - │ ├── d/g/h [value=users-c-d-g-h]* - │ └── f/g/h [value=users-c-f-g-h]* - └── test/ [indices=h中] - ├── hello [value=test-hello]* - └── 中 [indices=文/] - ├── 文/abc [value=test-中文-abc]* - └── /test [value=test-中-test]* - """); - } - - @Test - void delete() { - RadixTree radixTree = new RadixTree<>(); - radixTree.insert("/", "index"); - radixTree.insert("/categories/default", "categories-default"); - radixTree.insert("/tags/halo", "tags-halo"); - radixTree.insert("/archives/hello-halo", "archives-hello-halo"); - radixTree.insert("/about", "about"); - radixTree.delete("/tags/halo"); - radixTree.delete("/archives/hello-halo"); - radixTree.insert("/tags/halo", "tags-halo"); - radixTree.delete("/"); - - String display = radixTree.display(); - assertThat(display).isEqualTo(""" - / [indices=cat] - ├── categories/default [value=categories-default]* - ├── about [value=about]* - └── tags/halo [value=tags-halo]* - """); - - radixTree.checkIndices(); - } - - @Test - void getSize() { - RadixTree radixTree = new RadixTree<>(); - radixTree.insert("/", "index"); - radixTree.insert("/categories/default", "categories-default"); - radixTree.insert("/tags/halo", "tags-halo"); - radixTree.insert("/archives/hello-halo", "archives-hello-halo"); - - assertThat(radixTree.getSize()).isEqualTo(4); - - radixTree.insert("/about", "about"); - radixTree.delete("/tags/halo"); - assertThat(radixTree.getSize()).isEqualTo(4); - - radixTree.delete("/archives/hello-halo"); - radixTree.insert("/tags/halo", "tags-halo"); - radixTree.delete("/"); - assertThat(radixTree.getSize()).isEqualTo(3); - } - - @Test - void contains() { - RadixTree radixTree = new RadixTree<>(); - radixTree.insert("/", "index"); - radixTree.insert("/categories/default", "categories-default"); - radixTree.insert("/tags/halo", "tags-halo"); - radixTree.insert("/archives/hello-halo", "archives-hello-halo"); - - assertThat(radixTree.contains("/tags/halo")).isTrue(); - assertThat(radixTree.contains("/archives/hello-halo")).isTrue(); - assertThat(radixTree.contains("/categories/default")).isTrue(); - - assertThat(radixTree.contains("/tags/test")).isFalse(); - assertThat(radixTree.contains("/tags/abc")).isFalse(); - assertThat(radixTree.contains("/archives/abc")).isFalse(); - assertThat(radixTree.contains("/archives")).isFalse(); - } - - @Test - void replace() { - RadixTree radixTree = new RadixTree<>(); - radixTree.insert("/", "index"); - radixTree.insert("/categories/default", "categories-default"); - - boolean replaced = radixTree.replace("/categories/default", "categories-new"); - assertThat(replaced).isTrue(); - assertThat(radixTree.find("/categories/default")).isEqualTo("categories-new"); - } - - @Test - void find() { - RadixTree radixTree = new RadixTree<>(); - for (String testCase : testCases()) { - radixTree.insert(testCase, testCase); - } - - for (String testCase : testCases()) { - String s = radixTree.find(testCase); - assertThat(s).isEqualTo(testCase); - } - } - - @Test - void visitTimes() { - AtomicInteger visitCount = new AtomicInteger(0); - RadixTree radixTree = new RadixTree<>() { - @Override - protected void visit(String prefix, Visitor visitor, - RadixTreeNode parent, RadixTreeNode node) { - visitCount.getAndIncrement(); - super.visit(prefix, visitor, parent, node); - } - }; - - for (String testCase : testCases()) { - radixTree.insert(testCase, testCase); - } - - /* - * / [indices=hbAscxy01adnΠuvw] 1 - * ├── s [indices=er] 2 - │ ├── earch/query [value=/search/query]* 3 - │ └── rc/*filepath [value=/src/*filepath]* 3 - * ├── u [indices=/s] 2 - * │ └── sers/a/b/c [indices=/c] 3 - * │ ├── /d [value=/users/a/b/c/d]* 4 - * │ └── c/d [value=/users/a/b/cc/d]* 4 - * //... - */ - - String key = "/users/a/b/c/d"; - AtomicInteger resultVisitorCount = new AtomicInteger(0); - RadixTree.Visitor visitor = new RadixTree.Visitor<>() { - public void visit(String key, RadixTreeNode parent, - RadixTreeNode node) { - resultVisitorCount.getAndIncrement(); - if (node.isReal()) { - result = node.getValue(); - } - } - }; - - RadixTreeNode root = radixTree.getRoot(); - radixTree.visit(key, visitor, null, root); - assertThat(resultVisitorCount.get()).isEqualTo(1); - assertThat(visitor.result).isEqualTo(key); - assertThat(visitCount.get()).isEqualTo(4); - - // clear counter - visitCount.set(0); - resultVisitorCount.set(0); - visitor.result = null; - key = "/search/query"; - radixTree.visit(key, visitor, null, root); - assertThat(resultVisitorCount.get()).isEqualTo(1); - assertThat(visitCount.get()).isEqualTo(3); - assertThat(visitor.getResult()).isEqualTo(key); - - // clear counter - visitCount.set(0); - resultVisitorCount.set(0); - visitor.result = null; - // not exists key - key = "/search"; - radixTree.visit(key, visitor, null, root); - assertThat(resultVisitorCount.get()).isEqualTo(0); - assertThat(visitCount.get()).isEqualTo(3); - assertThat(visitor.getResult()).isEqualTo(null); - - // clear counter - visitCount.set(0); - resultVisitorCount.set(0); - visitor.result = null; - // not exists key - key = "/s"; - radixTree.visit(key, visitor, null, root); - assertThat(resultVisitorCount.get()).isEqualTo(1); - assertThat(visitCount.get()).isEqualTo(2); - assertThat(visitor.getResult()).isEqualTo(null); - } - - private List testCases() { - return Arrays.asList( - "/hi", - "/b/", - "/ABC/", - "/search/query", - "/cmd/tool/", - "/src/*filepath", - "/x", - "/x/y", - "/y/", - "/y/z", - "/0/id", - "/0/id/1", - "/1/id/", - "/1/id/2", - "/aa", - "/a/", - "/doc", - "/doc/go_faq.html", - "/doc/go1.html", - "/doc/go/away", - "/no/a", - "/no/b", - "/Π", - "/u/apfêl/", - "/u/äpfêl/", - "/u/öpfêl", - "/v/Äpfêl/", - "/v/Öpfêl", - "/w/♬", - "/w/♭/", - "/w/𠜎", - "/w/𠜏/", - "/users/a/b/c/d", - "/users/a/b/cc/d" - ); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java b/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java index 749097383b8..f069e7dfbea 100644 --- a/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java +++ b/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java @@ -22,7 +22,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import run.halo.app.theme.HaloViewResolver; -import run.halo.app.theme.router.strategy.EmptyView; /** * Tests for {@link ViewNameResolver}. diff --git a/src/test/java/run/halo/app/theme/router/factories/ArchiveRouteFactoryTest.java b/src/test/java/run/halo/app/theme/router/factories/ArchiveRouteFactoryTest.java new file mode 100644 index 00000000000..81d99e1d52f --- /dev/null +++ b/src/test/java/run/halo/app/theme/router/factories/ArchiveRouteFactoryTest.java @@ -0,0 +1,66 @@ +package run.halo.app.theme.router.factories; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import run.halo.app.theme.finders.PostFinder; + +/** + * Tests for {@link ArchiveRouteFactory}. + * + * @author guqing + * @since 2.0.0 + */ +@ExtendWith(MockitoExtension.class) +class ArchiveRouteFactoryTest extends RouteFactoryTestSuite { + @Mock + private PostFinder postFinder; + + @InjectMocks + private ArchiveRouteFactory archiveRouteFactory; + + @Test + void create() { + String prefix = "/new-archives"; + RouterFunction routerFunction = archiveRouteFactory.create(prefix); + WebTestClient client = getWebTestClient(routerFunction); + + client.get() + .uri(prefix) + .exchange() + .expectStatus().isOk(); + + client.get() + .uri(prefix + "/page/1") + .exchange() + .expectStatus().isOk(); + + client.get() + .uri(prefix + "/2022/09") + .exchange() + .expectStatus().isOk(); + + client.get() + .uri(prefix + "/2022/08/page/1") + .exchange() + .expectStatus().isOk(); + + client.get() + .uri(prefix + "/2022/8/page/1") + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.NOT_FOUND); + + client.get() + .uri("/nothing") + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.NOT_FOUND); + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/factories/AuthorPostsRouteFactoryTest.java b/src/test/java/run/halo/app/theme/router/factories/AuthorPostsRouteFactoryTest.java new file mode 100644 index 00000000000..cd2b10773c2 --- /dev/null +++ b/src/test/java/run/halo/app/theme/router/factories/AuthorPostsRouteFactoryTest.java @@ -0,0 +1,43 @@ +package run.halo.app.theme.router.factories; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.User; +import run.halo.app.extension.ReactiveExtensionClient; + +/** + * Tests for {@link AuthorPostsRouteFactory}. + * + * @author guqing + * @since 2.0.0 + */ +@ExtendWith(MockitoExtension.class) +class AuthorPostsRouteFactoryTest extends RouteFactoryTestSuite { + @Mock + ReactiveExtensionClient client; + @InjectMocks + AuthorPostsRouteFactory authorPostsRouteFactory; + + @Test + void create() { + RouterFunction routerFunction = authorPostsRouteFactory.create(null); + WebTestClient webClient = getWebTestClient(routerFunction); + + when(client.get(eq(User.class), eq("fake-user"))) + .thenReturn(Mono.just(new User())); + webClient.get() + .uri("/authors/fake-user") + .exchange() + .expectStatus().isOk(); + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/factories/CategoriesRouteFactoryTest.java b/src/test/java/run/halo/app/theme/router/factories/CategoriesRouteFactoryTest.java new file mode 100644 index 00000000000..8b2066d11d4 --- /dev/null +++ b/src/test/java/run/halo/app/theme/router/factories/CategoriesRouteFactoryTest.java @@ -0,0 +1,41 @@ +package run.halo.app.theme.router.factories; + +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Flux; +import run.halo.app.theme.finders.CategoryFinder; + +/** + * Tests for {@link CategoriesRouteFactory}. + * + * @author guqing + * @since 2.0.0 + */ +class CategoriesRouteFactoryTest extends RouteFactoryTestSuite { + + @Mock + private CategoryFinder categoryFinder; + + @InjectMocks + private CategoriesRouteFactory categoriesRouteFactory; + + @Test + void create() { + String prefix = "/topics"; + RouterFunction routerFunction = categoriesRouteFactory.create(prefix); + WebTestClient webClient = getWebTestClient(routerFunction); + + when(categoryFinder.listAsTree()) + .thenReturn(Flux.empty()); + webClient.get() + .uri(prefix) + .exchange() + .expectStatus().isOk(); + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/factories/IndexRouteFactoryTest.java b/src/test/java/run/halo/app/theme/router/factories/IndexRouteFactoryTest.java new file mode 100644 index 00000000000..a7983d0dc2e --- /dev/null +++ b/src/test/java/run/halo/app/theme/router/factories/IndexRouteFactoryTest.java @@ -0,0 +1,42 @@ +package run.halo.app.theme.router.factories; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import run.halo.app.theme.finders.PostFinder; + +/** + * Tests for {@link IndexRouteFactory}. + * + * @author guqing + * @since 2.0.0 + */ +@ExtendWith(MockitoExtension.class) +class IndexRouteFactoryTest extends RouteFactoryTestSuite { + @Mock + private PostFinder postFinder; + + @InjectMocks + private IndexRouteFactory indexRouteFactory; + + @Test + void create() { + RouterFunction routerFunction = indexRouteFactory.create("/"); + WebTestClient webTestClient = getWebTestClient(routerFunction); + + webTestClient.get() + .uri("/") + .exchange() + .expectStatus().isOk(); + + webTestClient.get() + .uri("/page/1") + .exchange() + .expectStatus().isOk(); + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/RouterStrategyTestSuite.java b/src/test/java/run/halo/app/theme/router/factories/RouteFactoryTestSuite.java similarity index 72% rename from src/test/java/run/halo/app/theme/router/strategy/RouterStrategyTestSuite.java rename to src/test/java/run/halo/app/theme/router/factories/RouteFactoryTestSuite.java index 9f1959f0271..a08dabffdd2 100644 --- a/src/test/java/run/halo/app/theme/router/strategy/RouterStrategyTestSuite.java +++ b/src/test/java/run/halo/app/theme/router/factories/RouteFactoryTestSuite.java @@ -1,17 +1,14 @@ -package run.halo.app.theme.router.strategy; +package run.halo.app.theme.router.factories; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; -import java.net.URI; import java.net.URISyntaxException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.context.ApplicationContext; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.server.HandlerStrategies; import org.springframework.web.reactive.function.server.RouterFunction; @@ -20,38 +17,27 @@ import reactor.core.publisher.Mono; import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.infra.SystemSetting; -import run.halo.app.infra.properties.HaloProperties; -import run.halo.app.theme.router.PermalinkHttpGetRouter; +import run.halo.app.theme.router.EmptyView; /** - * Abstract test for {@link DetailsPageRouteHandlerStrategy} and - * {@link ListPageRouteHandlerStrategy}. + * Abstract test for {@link RouteFactory}. * * @author guqing * @since 2.0.0 */ @ExtendWith(MockitoExtension.class) -abstract class RouterStrategyTestSuite { +abstract class RouteFactoryTestSuite { @Mock protected SystemConfigurableEnvironmentFetcher environmentFetcher; - @Mock - protected ApplicationContext applicationContext; - @Mock - protected HaloProperties haloProperties; - @Mock protected ViewResolver viewResolver; - @InjectMocks - protected PermalinkHttpGetRouter permalinkHttpGetRouter; - @BeforeEach final void setUpParent() throws URISyntaxException { lenient().when(environmentFetcher.fetchPost()) .thenReturn(Mono.just(new SystemSetting.Post())); lenient().when(environmentFetcher.fetch(eq(SystemSetting.ThemeRouteRules.GROUP), eq(SystemSetting.ThemeRouteRules.class))).thenReturn(Mono.just(getThemeRouteRules())); - lenient().when(haloProperties.getExternalUrl()).thenReturn(new URI("http://example.com")); lenient().when(viewResolver.resolveViewName(any(), any())) .thenReturn(Mono.just(new EmptyView())); setUp(); @@ -77,8 +63,4 @@ public WebTestClient getWebTestClient(RouterFunction routeFuncti .build()) .build(); } - - public RouterFunction getRouterFunction() { - return request -> Mono.justOrEmpty(permalinkHttpGetRouter.route(request)); - } } diff --git a/src/test/java/run/halo/app/theme/router/factories/TagPostRouteFactoryTest.java b/src/test/java/run/halo/app/theme/router/factories/TagPostRouteFactoryTest.java new file mode 100644 index 00000000000..1110880bd15 --- /dev/null +++ b/src/test/java/run/halo/app/theme/router/factories/TagPostRouteFactoryTest.java @@ -0,0 +1,68 @@ +package run.halo.app.theme.router.factories; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.content.Tag; +import run.halo.app.extension.Metadata; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.theme.finders.PostFinder; +import run.halo.app.theme.finders.TagFinder; +import run.halo.app.theme.finders.vo.TagVo; + +/** + * Tests for @link TagPostRouteFactory}. + * + * @author guqing + * @since 2.0.0 + */ +@ExtendWith(MockitoExtension.class) +class TagPostRouteFactoryTest extends RouteFactoryTestSuite { + @Mock + private ReactiveExtensionClient client; + @Mock + private TagFinder tagFinder; + @Mock + private PostFinder postFinder; + + @InjectMocks + TagPostRouteFactory tagPostRouteFactory; + + @Test + void create() { + when(client.list(eq(Tag.class), any(), eq(null))).thenReturn(Flux.empty()); + WebTestClient webTestClient = getWebTestClient(tagPostRouteFactory.create("/new-tags")); + + webTestClient.get() + .uri("/new-tags/tag-slug-1") + .exchange() + .expectStatus().isNotFound(); + + Tag tag = new Tag(); + tag.setMetadata(new Metadata()); + tag.getMetadata().setName("fake-tag-name"); + tag.setSpec(new Tag.TagSpec()); + tag.getSpec().setSlug("tag-slug-2"); + when(client.list(eq(Tag.class), any(), eq(null))).thenReturn(Flux.just(tag)); + when(tagFinder.getByName(eq(tag.getMetadata().getName()))) + .thenReturn(Mono.just(TagVo.from(tag))); + webTestClient.get() + .uri("/new-tags/tag-slug-2") + .exchange() + .expectStatus().isOk(); + + webTestClient.get() + .uri("/new-tags/tag-slug-2/page/1") + .exchange() + .expectStatus().isOk(); + } +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategyTest.java deleted file mode 100644 index 854991657ec..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategyTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.theme.finders.PostFinder; - -/** - * Tests for {@link ArchivesRouteStrategy}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class ArchivesRouteStrategyTest extends RouterStrategyTestSuite { - @Mock - private PostFinder postFinder; - - @InjectMocks - private ArchivesRouteStrategy archivesRouteStrategy; - - @Test - void getRouteFunctionWhenDefaultPattern() { - HandlerFunction handler = archivesRouteStrategy.getHandler(); - RouterFunction routeFunction = getRouterFunction(); - - WebTestClient client = getWebTestClient(routeFunction); - - List routerPaths = archivesRouteStrategy.getRouterPaths("/archives"); - for (String routerPath : routerPaths) { - permalinkHttpGetRouter.insert(routerPath, handler); - } - - fixedAssertion(client, "/archives"); - - client.get() - .uri("/nothing") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } - - private static void fixedAssertion(WebTestClient client, String prefix) { - client.get() - .uri(prefix) - .exchange() - .expectStatus().isOk(); - - client.get() - .uri(prefix + "/page/1") - .exchange() - .expectStatus().isOk(); - - client.get() - .uri(prefix + "/2022/09") - .exchange() - .expectStatus().isOk(); - - client.get() - .uri(prefix + "/2022/08/page/1") - .exchange() - .expectStatus().isOk(); - - client.get() - .uri(prefix + "/2022/8/page/1") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } - - @Test - void getRouteFunctionWhenOtherPattern() { - HandlerFunction handler = archivesRouteStrategy.getHandler(); - RouterFunction routeFunction = getRouterFunction(); - - final WebTestClient client = getWebTestClient(routeFunction); - - List routerPaths = archivesRouteStrategy.getRouterPaths("/archives-test"); - for (String routerPath : routerPaths) { - permalinkHttpGetRouter.insert(routerPath, handler); - } - - fixedAssertion(client, "/archives-test"); - - client.get() - .uri("/archives") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/AuthorRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/AuthorRouteStrategyTest.java deleted file mode 100644 index 7426d85fc54..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/AuthorRouteStrategyTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import java.util.Map; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.springframework.http.MediaType; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; -import run.halo.app.core.extension.User; -import run.halo.app.extension.Metadata; -import run.halo.app.extension.ReactiveExtensionClient; -import run.halo.app.theme.DefaultTemplateEnum; - -/** - * Tests for {@link AuthorRouteStrategy}. - * - * @author guqing - * @since 2.0.1 - */ -class AuthorRouteStrategyTest extends RouterStrategyTestSuite { - - @Mock - private ReactiveExtensionClient client; - - @InjectMocks - private AuthorRouteStrategy strategy; - - @Test - void handlerTest() { - User user = new User(); - Metadata metadata = new Metadata(); - metadata.setName("fake-user"); - user.setMetadata(metadata); - user.setSpec(new User.UserSpec()); - - when(client.fetch(eq(User.class), eq("fake-user"))).thenReturn(Mono.just(user)); - permalinkHttpGetRouter.insert("/authors/fake-user", - strategy.getHandler(getThemeRouteRules(), "fake-user")); - - when(viewResolver.resolveViewName(eq(DefaultTemplateEnum.AUTHOR.getValue()), any())) - .thenReturn(Mono.just(new EmptyView() { - @Override - public Mono render(Map model, MediaType contentType, - ServerWebExchange exchange) { - assertThat(model.get("name")).isEqualTo("fake-user"); - assertThat(model.get("_templateId")) - .isEqualTo(DefaultTemplateEnum.AUTHOR.getValue()); - assertThat(model.get("author")).isNotNull(); - assertThat(model.get("posts")).isNotNull(); - return Mono.empty(); - } - })); - - WebTestClient webTestClient = getWebTestClient(getRouterFunction()); - webTestClient.get() - .uri("/authors/fake-user") - .exchange() - .expectStatus() - .isOk(); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/CategoriesRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/CategoriesRouteStrategyTest.java deleted file mode 100644 index eae360e266b..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/CategoriesRouteStrategyTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.mockito.Mockito.when; - -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Flux; -import run.halo.app.theme.finders.CategoryFinder; - -/** - * Tests for {@link CategoriesRouteStrategy}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class CategoriesRouteStrategyTest extends RouterStrategyTestSuite { - @Mock - private CategoryFinder categoryFinder; - - @InjectMocks - private CategoriesRouteStrategy categoriesRouteStrategy; - - @Test - void getRouteFunction() { - HandlerFunction handler = categoriesRouteStrategy.getHandler(); - RouterFunction routeFunction = getRouterFunction(); - - WebTestClient client = getWebTestClient(routeFunction); - - List routerPaths = categoriesRouteStrategy.getRouterPaths("/categories-test"); - for (String routerPath : routerPaths) { - permalinkHttpGetRouter.insert(routerPath, handler); - } - - when(categoryFinder.listAsTree()).thenReturn(Flux.empty()); - client.get() - .uri("/categories-test") - .exchange() - .expectStatus() - .isOk(); - - client.get() - .uri("/nothing") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/CategoryRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/CategoryRouteStrategyTest.java deleted file mode 100644 index a07946e6b60..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/CategoryRouteStrategyTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.theme.finders.CategoryFinder; -import run.halo.app.theme.finders.PostFinder; - -/** - * Tests for {@link CategoryRouteStrategy}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class CategoryRouteStrategyTest extends RouterStrategyTestSuite { - - @Mock - private PostFinder postFinder; - - @Mock - private CategoryFinder categoryFinder; - - @InjectMocks - private CategoryRouteStrategy categoryRouteStrategy; - - @Test - void getRouteFunction() { - RouterFunction routeFunction = getRouterFunction(); - final WebTestClient client = getWebTestClient(routeFunction); - - permalinkHttpGetRouter.insert("/categories-test/category-slug-1", - categoryRouteStrategy.getHandler(null, "category-slug-1")); - permalinkHttpGetRouter.insert("/categories-test/category-slug-2", - categoryRouteStrategy.getHandler(null, "category-slug-2")); - - when(categoryFinder.getByName(any())).thenReturn(Mono.empty()); - - // /{prefix}/{slug} - client.get() - .uri("/categories-test/category-slug-1") - .exchange() - .expectStatus() - .isOk(); - - // /{prefix}/{slug} - client.get() - .uri("/categories-test/category-slug-2") - .exchange() - .expectStatus() - .isOk(); - - // /{prefix}/{slug}/page/{page} - client.get() - .uri("/categories-test/category-slug-2/page/1") - .exchange() - .expectStatus() - .isOk(); - - client.get() - .uri("/categories-test/not-exist-slug") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - - client.get() - .uri("/nothing") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } -} \ No newline at end of file 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 deleted file mode 100644 index 1ae853c43fa..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/IndexRouteStrategyTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.theme.finders.PostFinder; - -/** - * Tests for {@link IndexRouteStrategy}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class IndexRouteStrategyTest extends RouterStrategyTestSuite { - @Mock - private PostFinder postFinder; - - @InjectMocks - private IndexRouteStrategy indexRouteStrategy; - - @Test - void getRouteFunction() { - HandlerFunction handler = indexRouteStrategy.getHandler(); - RouterFunction routeFunction = getRouterFunction(); - - List routerPaths = indexRouteStrategy.getRouterPaths("/"); - for (String routerPath : routerPaths) { - permalinkHttpGetRouter.insert(routerPath, handler); - } - - final WebTestClient client = getWebTestClient(routeFunction); - - client.get() - .uri("/") - .exchange() - .expectStatus() - .isOk(); - - client.get() - .uri("/page/1") - .exchange() - .expectStatus() - .isOk(); - - client.get() - .uri("/nothing") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/PostRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/PostRouteStrategyTest.java deleted file mode 100644 index 454f26e204d..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/PostRouteStrategyTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.lenient; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.content.TestPost; -import run.halo.app.infra.SystemSetting; -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.router.ViewNameResolver; - -/** - * Tests for {@link PostRouteStrategy}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class PostRouteStrategyTest extends RouterStrategyTestSuite { - - @Mock - private PostFinder postFinder; - - @Mock - private ViewNameResolver viewNameResolver; - - @InjectMocks - private PostRouteStrategy postRouteStrategy; - - @Override - public void setUp() { - lenient().when(viewNameResolver.resolveViewNameOrDefault(any(), any(), any())) - .thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue())); - lenient().when(postFinder.getByName(any())) - .thenReturn(Mono.just(PostVo.from(TestPost.postV1()))); - } - - @Test - void getRouteFunctionWhenSlugPathVariable() { - RouterFunction routeFunction = getRouterFunction(); - - SystemSetting.ThemeRouteRules themeRouteRules = getThemeRouteRules(); - themeRouteRules.setPost("/posts-test/{slug}"); - permalinkHttpGetRouter.insert("/posts-test/fake-slug", - postRouteStrategy.getHandler(themeRouteRules, "fake-slug")); - - WebTestClient client = getWebTestClient(routeFunction); - - client.get() - .uri("/posts-test/fake-slug") - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void getRouteFunctionWhenNamePathVariable() { - RouterFunction routeFunction = getRouterFunction(); - - SystemSetting.ThemeRouteRules themeRouteRules = getThemeRouteRules(); - themeRouteRules.setPost("/posts-test/{slug}"); - permalinkHttpGetRouter.insert("/posts-test/fake-name", - postRouteStrategy.getHandler(themeRouteRules, "fake-name")); - - WebTestClient client = getWebTestClient(routeFunction); - - client.get() - .uri("/posts-test/fake-name") - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void getRouteFunctionWhenYearMonthSlugPathVariable() { - RouterFunction routeFunction = getRouterFunction(); - - SystemSetting.ThemeRouteRules themeRouteRules = getThemeRouteRules(); - themeRouteRules.setPost("/{year}/{month}/{slug}"); - permalinkHttpGetRouter.insert("/{year}/{month}/{slug}", - postRouteStrategy.getHandler(themeRouteRules, "fake-name")); - - WebTestClient client = getWebTestClient(routeFunction); - - client.get() - .uri("/2022/08/fake-slug") - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void getRouteFunctionWhenQueryParam() { - RouterFunction routeFunction = getRouterFunction(); - - SystemSetting.ThemeRouteRules themeRouteRules = getThemeRouteRules(); - themeRouteRules.setPost("/?p={slug}"); - permalinkHttpGetRouter.insert("/?p=fake-name", - postRouteStrategy.getHandler(themeRouteRules, "fake-name")); - - WebTestClient client = getWebTestClient(routeFunction); - - client.get() - .uri(uriBuilder -> uriBuilder.path("/") - .queryParam("p", "fake-name") - .build() - ) - .exchange() - .expectStatus() - .isOk(); - - client.get() - .uri(uriBuilder -> uriBuilder.path("/") - .queryParam("p", "nothing") - .build() - ) - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategyTest.java deleted file mode 100644 index 6dd836186e2..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategyTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.when; -import static run.halo.app.theme.DefaultTemplateEnum.SINGLE_PAGE; - -import java.util.Map; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.MediaType; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; -import run.halo.app.core.extension.content.SinglePage; -import run.halo.app.extension.GroupVersionKind; -import run.halo.app.extension.Metadata; -import run.halo.app.theme.finders.SinglePageFinder; -import run.halo.app.theme.finders.vo.SinglePageVo; -import run.halo.app.theme.router.ViewNameResolver; - -/** - * Tests for {@link SinglePageRouteStrategy}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class SinglePageRouteStrategyTest extends RouterStrategyTestSuite { - @Mock - private SinglePageFinder singlePageFinder; - - @Mock - private ViewNameResolver viewNameResolver; - - @InjectMocks - private SinglePageRouteStrategy strategy; - - - @Override - public void setUp() { - lenient().when(viewResolver.resolveViewName(eq(SINGLE_PAGE.getValue()), any())) - .thenReturn(Mono.just(new EmptyView())); - } - - @Test - void shouldResponse404IfNoPermalinkFound() { - createClient().get() - .uri("/nothing") - .exchange() - .expectStatus().isNotFound(); - } - - @Test - void shouldResponse200IfPermalinkFound() { - permalinkHttpGetRouter.insert("/fake-slug", - strategy.getHandler(getThemeRouteRules(), "fake-name")); - when(singlePageFinder.getByName(any())).thenReturn(Mono.empty()); - createClient().get() - .uri("/fake-slug") - .exchange() - .expectStatus() - .isOk(); - } - - @Test - void shouldResponse200IfSlugNameContainsSpecialChars() { - permalinkHttpGetRouter.insert("/fake / slug", - strategy.getHandler(getThemeRouteRules(), "fake-name")); - - when(singlePageFinder.getByName(any())).thenReturn(Mono.empty()); - createClient().get() - .uri("/fake / slug") - .exchange() - .expectStatus().isOk(); - } - - @Test - void shouldResponse200IfSlugNameContainsChineseChars() { - permalinkHttpGetRouter.insert("/中文", - strategy.getHandler(getThemeRouteRules(), "fake-name")); - - when(singlePageFinder.getByName(any())).thenReturn(Mono.empty()); - - createClient().get() - .uri("/中文") - .exchange() - .expectStatus().isOk(); - } - - @Test - void ensureModel() { - // fix gh-2912 - Metadata metadata = new Metadata(); - metadata.setName("fake-name"); - SinglePageVo singlePageVo = SinglePageVo.builder() - .metadata(metadata) - .spec(new SinglePage.SinglePageSpec()) - .build(); - - when(singlePageFinder.getByName(eq("fake-name"))).thenReturn(Mono.just(singlePageVo)); - permalinkHttpGetRouter.insert("/fake-slug", - strategy.getHandler(getThemeRouteRules(), "fake-name")); - - when(viewNameResolver.resolveViewNameOrDefault(any(), any(), any())) - .thenReturn(Mono.just("page")); - when(viewResolver.resolveViewName(eq(SINGLE_PAGE.getValue()), any())) - .thenReturn(Mono.just(new EmptyView() { - @Override - public Mono render(Map model, MediaType contentType, - ServerWebExchange exchange) { - assertThat(model.get("name")).isEqualTo("fake-name"); - assertThat(model.get("plural")).isEqualTo("singlepages"); - assertThat(model.get("_templateId")).isEqualTo("page"); - assertThat(model.get("singlePage")).isEqualTo(singlePageVo); - assertThat(model.get("groupVersionKind")).isEqualTo( - GroupVersionKind.fromExtension(SinglePage.class)); - return Mono.empty(); - } - })); - createClient().get() - .uri("/fake-slug") - .exchange() - .expectStatus().isOk() - .expectBody(); - } - - WebTestClient createClient() { - RouterFunction routeFunction = getRouterFunction(); - return getWebTestClient(routeFunction); - } -} diff --git a/src/test/java/run/halo/app/theme/router/strategy/TagRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/TagRouteStrategyTest.java deleted file mode 100644 index ddb8d37a818..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/TagRouteStrategyTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.theme.finders.PostFinder; -import run.halo.app.theme.finders.TagFinder; - -/** - * Tests for {@link TagRouteStrategy}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class TagRouteStrategyTest extends RouterStrategyTestSuite { - - @Mock - private PostFinder postFinder; - @Mock - private TagFinder tagFinder; - - @InjectMocks - private TagRouteStrategy tagRouteStrategy; - - @Test - void getRouteFunction() { - RouterFunction routeFunction = getRouterFunction(); - WebTestClient client = getWebTestClient(routeFunction); - - permalinkHttpGetRouter.insert("/tags-test/fake-slug", - tagRouteStrategy.getHandler(getThemeRouteRules(), "fake-name")); - when(tagFinder.getByName(any())).thenReturn(Mono.empty()); - - client.get() - .uri("/tags-test/fake-slug") - .exchange() - .expectStatus() - .isOk(); - - client.get() - .uri("/tags-test/fake-slug/page/1") - .exchange() - .expectStatus() - .isOk(); - - client.get() - .uri("/nothing") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } -} \ No newline at end of file diff --git a/src/test/java/run/halo/app/theme/router/strategy/TagsRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/TagsRouteStrategyTest.java deleted file mode 100644 index a30b4fd34db..00000000000 --- a/src/test/java/run/halo/app/theme/router/strategy/TagsRouteStrategyTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package run.halo.app.theme.router.strategy; - -import static org.mockito.Mockito.when; - -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Flux; -import run.halo.app.theme.finders.TagFinder; - -/** - * Tests for {@link TagsRouteStrategy}. - * - * @author guqing - * @since 2.0.0 - */ -@ExtendWith(MockitoExtension.class) -class TagsRouteStrategyTest extends RouterStrategyTestSuite { - - @Mock - private TagFinder tagFinder; - - @InjectMocks - private TagsRouteStrategy tagsRouteStrategy; - - @Test - void getRouteFunction() { - RouterFunction routeFunction = getRouterFunction(); - WebTestClient client = getWebTestClient(routeFunction); - - List routerPaths = tagsRouteStrategy.getRouterPaths("/tags-test"); - HandlerFunction handler = tagsRouteStrategy.getHandler(); - for (String routerPath : routerPaths) { - permalinkHttpGetRouter.insert(routerPath, handler); - } - when(tagFinder.listAll()).thenReturn(Flux.empty()); - - client.get() - .uri("/tags-test") - .exchange() - .expectStatus() - .isOk(); - - client.get() - .uri("/nothing") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.NOT_FOUND); - } -} \ No newline at end of file