mirror of https://github.com/halo-dev/halo
fix: permalink update when slug changed (#2382)
<!-- Thanks for sending a pull request! Here are some tips for you: 1. 如果这是你的第一次,请阅读我们的贡献指南:<https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>。 1. If this is your first time, please read our contributor guidelines: <https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>. 2. 请根据你解决问题的类型为 Pull Request 添加合适的标签。 2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. 3. 请确保你已经添加并运行了适当的测试。 3. Ensure you have added or ran the appropriate tests for your PR. --> #### What type of PR is this? /kind bug /area core /milestone 2.0 <!-- 添加其中一个类别: Add one of the following kinds: /kind bug /kind cleanup /kind documentation /kind feature /kind improvement 适当添加其中一个或多个类别(可选): Optionally add one or more of the following kinds if applicable: /kind api-change /kind deprecation /kind failing-test /kind flake /kind regression --> #### What this PR does / why we need it: 修复文章 分类 标签的 slug 改变时,没有重新生成 permalink 的问题 #### Which issue(s) this PR fixes: <!-- PR 合并时自动关闭 issue。 Automatically closes linked issue when PR is merged. 用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)` Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> Fixes # #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? <!-- 如果当前 Pull Request 的修改不会造成用户侧的任何变更,在 `release-note` 代码块儿中填写 `NONE`。 否则请填写用户侧能够理解的 Release Note。如果当前 Pull Request 包含破坏性更新(Break Change), Release Note 需要以 `action required` 开头。 If no, just write "NONE" in the release-note block below. If yes, a release note is required: Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required". --> ```release-note None ```pull/2392/head
parent
969fcde641
commit
e25a3d2232
|
@ -1,6 +1,30 @@
|
||||||
package run.halo.app.content.permalinks;
|
package run.halo.app.content.permalinks;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import run.halo.app.extension.GroupVersionKind;
|
import run.halo.app.extension.GroupVersionKind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slug can be modified, so it is not included in {@link #equals(Object)} and {@link #hashCode()}.
|
||||||
|
*
|
||||||
|
* @param gvk group version kind
|
||||||
|
* @param name extension name
|
||||||
|
* @param slug extension slug
|
||||||
|
*/
|
||||||
public record ExtensionLocator(GroupVersionKind gvk, String name, String slug) {
|
public record ExtensionLocator(GroupVersionKind gvk, String name, String slug) {
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ExtensionLocator locator = (ExtensionLocator) o;
|
||||||
|
return gvk.equals(locator.gvk) && name.equals(locator.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(gvk, name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension.reconciler;
|
package run.halo.app.core.extension.reconciler;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
|
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
|
||||||
import run.halo.app.core.extension.Category;
|
import run.halo.app.core.extension.Category;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
@ -13,7 +15,7 @@ import run.halo.app.infra.utils.JsonUtils;
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class CategoryReconciler implements Reconciler<Reconciler.Request> {
|
public class CategoryReconciler implements Reconciler<Reconciler.Request> {
|
||||||
|
private static final String FINALIZER_NAME = "category-protection";
|
||||||
private final ExtensionClient client;
|
private final ExtensionClient client;
|
||||||
private final CategoryPermalinkPolicy categoryPermalinkPolicy;
|
private final CategoryPermalinkPolicy categoryPermalinkPolicy;
|
||||||
|
|
||||||
|
@ -26,25 +28,67 @@ public class CategoryReconciler implements Reconciler<Reconciler.Request> {
|
||||||
@Override
|
@Override
|
||||||
public Result reconcile(Request request) {
|
public Result reconcile(Request request) {
|
||||||
client.fetch(Category.class, request.name())
|
client.fetch(Category.class, request.name())
|
||||||
|
.ifPresent(category -> {
|
||||||
|
if (isDeleted(category)) {
|
||||||
|
cleanUpResourcesAndRemoveFinalizer(request.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addFinalizerIfNecessary(category);
|
||||||
|
|
||||||
|
reconcileStatus(request.name());
|
||||||
|
});
|
||||||
|
return new Result(false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFinalizerIfNecessary(Category oldCategory) {
|
||||||
|
Set<String> finalizers = oldCategory.getMetadata().getFinalizers();
|
||||||
|
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.fetch(Category.class, oldCategory.getMetadata().getName())
|
||||||
|
.ifPresent(category -> {
|
||||||
|
Set<String> newFinalizers = category.getMetadata().getFinalizers();
|
||||||
|
if (newFinalizers == null) {
|
||||||
|
newFinalizers = new HashSet<>();
|
||||||
|
category.getMetadata().setFinalizers(newFinalizers);
|
||||||
|
}
|
||||||
|
newFinalizers.add(FINALIZER_NAME);
|
||||||
|
client.update(category);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
client.update(category);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reconcileStatus(String name) {
|
||||||
|
client.fetch(Category.class, name)
|
||||||
.ifPresent(category -> {
|
.ifPresent(category -> {
|
||||||
Category oldCategory = JsonUtils.deepCopy(category);
|
Category oldCategory = JsonUtils.deepCopy(category);
|
||||||
reconcilePermalink(category);
|
categoryPermalinkPolicy.onPermalinkDelete(oldCategory);
|
||||||
|
|
||||||
|
category.getStatusOrDefault()
|
||||||
|
.setPermalink(categoryPermalinkPolicy.permalink(category));
|
||||||
|
categoryPermalinkPolicy.onPermalinkAdd(category);
|
||||||
|
|
||||||
if (!oldCategory.equals(category)) {
|
if (!oldCategory.equals(category)) {
|
||||||
client.update(category);
|
client.update(category);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return new Result(false, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reconcilePermalink(Category category) {
|
private boolean isDeleted(Category category) {
|
||||||
category.getStatusOrDefault()
|
return category.getMetadata().getDeletionTimestamp() != null;
|
||||||
.setPermalink(categoryPermalinkPolicy.permalink(category));
|
|
||||||
|
|
||||||
if (category.getMetadata().getDeletionTimestamp() != null) {
|
|
||||||
categoryPermalinkPolicy.onPermalinkDelete(category);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
categoryPermalinkPolicy.onPermalinkAdd(category);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import run.halo.app.infra.utils.JsonUtils;
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class PostReconciler implements Reconciler<Reconciler.Request> {
|
public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
|
private static final String FINALIZER_NAME = "post-protection";
|
||||||
private final ExtensionClient client;
|
private final ExtensionClient client;
|
||||||
private final ContentService contentService;
|
private final ContentService contentService;
|
||||||
private final PostPermalinkPolicy postPermalinkPolicy;
|
private final PostPermalinkPolicy postPermalinkPolicy;
|
||||||
|
@ -48,39 +49,58 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
public Result reconcile(Request request) {
|
public Result reconcile(Request request) {
|
||||||
client.fetch(Post.class, request.name())
|
client.fetch(Post.class, request.name())
|
||||||
.ifPresent(post -> {
|
.ifPresent(post -> {
|
||||||
Post oldPost = JsonUtils.deepCopy(post);
|
if (isDeleted(post)) {
|
||||||
|
cleanUpResourcesAndRemoveFinalizer(request.name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addFinalizerIfNecessary(post);
|
||||||
|
|
||||||
doReconcile(post);
|
reconcileMetadata(request.name());
|
||||||
permalinkReconcile(post);
|
reconcileStatus(request.name());
|
||||||
|
});
|
||||||
|
return new Result(false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reconcileMetadata(String name) {
|
||||||
|
client.fetch(Post.class, name).ifPresent(post -> {
|
||||||
|
final Post oldPost = JsonUtils.deepCopy(post);
|
||||||
|
Post.PostSpec spec = post.getSpec();
|
||||||
|
|
||||||
|
// handle logic delete
|
||||||
|
Map<String, String> labels = getLabelsOrDefault(post);
|
||||||
|
if (Objects.equals(spec.getDeleted(), true)) {
|
||||||
|
labels.put(Post.DELETED_LABEL, Boolean.TRUE.toString());
|
||||||
|
} else {
|
||||||
|
labels.put(Post.DELETED_LABEL, Boolean.FALSE.toString());
|
||||||
|
}
|
||||||
|
// synchronize some fields to labels to query
|
||||||
|
labels.put(Post.PHASE_LABEL, post.getStatusOrDefault().getPhase());
|
||||||
|
labels.put(Post.VISIBLE_LABEL,
|
||||||
|
Objects.requireNonNullElse(spec.getVisible(), Post.VisibleEnum.PUBLIC).name());
|
||||||
|
labels.put(Post.OWNER_LABEL, spec.getOwner());
|
||||||
|
|
||||||
if (!oldPost.equals(post)) {
|
if (!oldPost.equals(post)) {
|
||||||
client.update(post);
|
client.update(post);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return new Result(false, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void permalinkReconcile(Post post) {
|
private void reconcileStatus(String name) {
|
||||||
|
client.fetch(Post.class, name).ifPresent(post -> {
|
||||||
|
final Post oldPost = JsonUtils.deepCopy(post);
|
||||||
|
postPermalinkPolicy.onPermalinkDelete(oldPost);
|
||||||
|
|
||||||
post.getStatusOrDefault()
|
post.getStatusOrDefault()
|
||||||
.setPermalink(postPermalinkPolicy.permalink(post));
|
.setPermalink(postPermalinkPolicy.permalink(post));
|
||||||
|
if (isPublished(post)) {
|
||||||
if (Objects.equals(true, post.getSpec().getDeleted())
|
|
||||||
|| post.getMetadata().getDeletionTimestamp() != null
|
|
||||||
|| Objects.equals(false, post.getSpec().getPublished())) {
|
|
||||||
postPermalinkPolicy.onPermalinkDelete(post);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
postPermalinkPolicy.onPermalinkAdd(post);
|
postPermalinkPolicy.onPermalinkAdd(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doReconcile(Post post) {
|
|
||||||
String name = post.getMetadata().getName();
|
|
||||||
Post.PostSpec spec = post.getSpec();
|
|
||||||
Post.PostStatus status = post.getStatusOrDefault();
|
Post.PostStatus status = post.getStatusOrDefault();
|
||||||
if (status.getPhase() == null) {
|
if (status.getPhase() == null) {
|
||||||
status.setPhase(Post.PostPhase.DRAFT.name());
|
status.setPhase(Post.PostPhase.DRAFT.name());
|
||||||
}
|
}
|
||||||
|
Post.PostSpec spec = post.getSpec();
|
||||||
// handle excerpt
|
// handle excerpt
|
||||||
Post.Excerpt excerpt = spec.getExcerpt();
|
Post.Excerpt excerpt = spec.getExcerpt();
|
||||||
if (excerpt == null) {
|
if (excerpt == null) {
|
||||||
|
@ -88,7 +108,6 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
excerpt.setAutoGenerate(true);
|
excerpt.setAutoGenerate(true);
|
||||||
spec.setExcerpt(excerpt);
|
spec.setExcerpt(excerpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (excerpt.getAutoGenerate()) {
|
if (excerpt.getAutoGenerate()) {
|
||||||
contentService.getContent(spec.getHeadSnapshot())
|
contentService.getContent(spec.getHeadSnapshot())
|
||||||
.subscribe(content -> {
|
.subscribe(content -> {
|
||||||
|
@ -118,7 +137,8 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
|
|
||||||
// update in progress status
|
// update in progress status
|
||||||
snapshots.stream()
|
snapshots.stream()
|
||||||
.filter(snapshot -> snapshot.getMetadata().getName().equals(headSnapshot))
|
.filter(
|
||||||
|
snapshot -> snapshot.getMetadata().getName().equals(headSnapshot))
|
||||||
.findAny()
|
.findAny()
|
||||||
.ifPresent(snapshot -> {
|
.ifPresent(snapshot -> {
|
||||||
status.setInProgress(!isPublished(snapshot));
|
status.setInProgress(!isPublished(snapshot));
|
||||||
|
@ -139,19 +159,42 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
status.setPhase(Post.PostPhase.DRAFT.name());
|
status.setPhase(Post.PostPhase.DRAFT.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle logic delete
|
if (!oldPost.equals(post)) {
|
||||||
Map<String, String> labels = getLabelsOrDefault(post);
|
client.update(post);
|
||||||
if (isDeleted(post)) {
|
|
||||||
labels.put(Post.DELETED_LABEL, Boolean.TRUE.toString());
|
|
||||||
// TODO do more about logic delete such as remove router
|
|
||||||
} else {
|
|
||||||
labels.put(Post.DELETED_LABEL, Boolean.FALSE.toString());
|
|
||||||
}
|
}
|
||||||
// synchronize some fields to labels to query
|
});
|
||||||
labels.put(Post.PHASE_LABEL, status.getPhase());
|
}
|
||||||
labels.put(Post.VISIBLE_LABEL,
|
|
||||||
Objects.requireNonNullElse(spec.getVisible(), Post.VisibleEnum.PUBLIC).name());
|
private void addFinalizerIfNecessary(Post oldPost) {
|
||||||
labels.put(Post.OWNER_LABEL, spec.getOwner());
|
Set<String> finalizers = oldPost.getMetadata().getFinalizers();
|
||||||
|
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.fetch(Post.class, oldPost.getMetadata().getName())
|
||||||
|
.ifPresent(post -> {
|
||||||
|
Set<String> newFinalizers = post.getMetadata().getFinalizers();
|
||||||
|
if (newFinalizers == null) {
|
||||||
|
newFinalizers = new HashSet<>();
|
||||||
|
post.getMetadata().setFinalizers(newFinalizers);
|
||||||
|
}
|
||||||
|
newFinalizers.add(FINALIZER_NAME);
|
||||||
|
client.update(post);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanUpResourcesAndRemoveFinalizer(String postName) {
|
||||||
|
client.fetch(Post.class, postName).ifPresent(post -> {
|
||||||
|
cleanUpResources(post);
|
||||||
|
if (post.getMetadata().getFinalizers() != null) {
|
||||||
|
post.getMetadata().getFinalizers().remove(FINALIZER_NAME);
|
||||||
|
}
|
||||||
|
client.update(post);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanUpResources(Post post) {
|
||||||
|
// remove permalink from permalink indexer
|
||||||
|
postPermalinkPolicy.onPermalinkDelete(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> getLabelsOrDefault(Post post) {
|
private Map<String, String> getLabelsOrDefault(Post post) {
|
||||||
|
@ -175,6 +218,10 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
return snapshot.getSpec().getPublishTime() != null;
|
return snapshot.getSpec().getPublishTime() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isPublished(Post post) {
|
||||||
|
return Objects.equals(true, post.getSpec().getPublished());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isDeleted(Post post) {
|
private boolean isDeleted(Post post) {
|
||||||
return Objects.equals(true, post.getSpec().getDeleted())
|
return Objects.equals(true, post.getSpec().getDeleted())
|
||||||
|| post.getMetadata().getDeletionTimestamp() != null;
|
|| post.getMetadata().getDeletionTimestamp() != null;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension.reconciler;
|
package run.halo.app.core.extension.reconciler;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import run.halo.app.content.permalinks.TagPermalinkPolicy;
|
import run.halo.app.content.permalinks.TagPermalinkPolicy;
|
||||||
import run.halo.app.core.extension.Tag;
|
import run.halo.app.core.extension.Tag;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
@ -13,6 +15,7 @@ import run.halo.app.infra.utils.JsonUtils;
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class TagReconciler implements Reconciler<Reconciler.Request> {
|
public class TagReconciler implements Reconciler<Reconciler.Request> {
|
||||||
|
private static final String FINALIZER_NAME = "tag-protection";
|
||||||
private final ExtensionClient client;
|
private final ExtensionClient client;
|
||||||
private final TagPermalinkPolicy tagPermalinkPolicy;
|
private final TagPermalinkPolicy tagPermalinkPolicy;
|
||||||
|
|
||||||
|
@ -25,25 +28,66 @@ public class TagReconciler implements Reconciler<Reconciler.Request> {
|
||||||
public Result reconcile(Request request) {
|
public Result reconcile(Request request) {
|
||||||
client.fetch(Tag.class, request.name())
|
client.fetch(Tag.class, request.name())
|
||||||
.ifPresent(tag -> {
|
.ifPresent(tag -> {
|
||||||
Tag oldTag = JsonUtils.deepCopy(tag);
|
if (isDeleted(tag)) {
|
||||||
|
cleanUpResourcesAndRemoveFinalizer(request.name());
|
||||||
this.reconcilePermalink(tag);
|
return;
|
||||||
|
|
||||||
if (!tag.equals(oldTag)) {
|
|
||||||
client.update(tag);
|
|
||||||
}
|
}
|
||||||
|
addFinalizerIfNecessary(tag);
|
||||||
|
|
||||||
|
this.reconcileStatus(request.name());
|
||||||
});
|
});
|
||||||
return new Result(false, null);
|
return new Result(false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reconcilePermalink(Tag tag) {
|
private void cleanUpResources(Tag tag) {
|
||||||
tag.getStatusOrDefault()
|
// remove permalink from permalink indexer
|
||||||
.setPermalink(tagPermalinkPolicy.permalink(tag));
|
|
||||||
|
|
||||||
if (tag.getMetadata().getDeletionTimestamp() != null) {
|
|
||||||
tagPermalinkPolicy.onPermalinkDelete(tag);
|
tagPermalinkPolicy.onPermalinkDelete(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFinalizerIfNecessary(Tag oldTag) {
|
||||||
|
Set<String> finalizers = oldTag.getMetadata().getFinalizers();
|
||||||
|
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
client.fetch(Tag.class, oldTag.getMetadata().getName())
|
||||||
|
.ifPresent(tag -> {
|
||||||
|
Set<String> newFinalizers = tag.getMetadata().getFinalizers();
|
||||||
|
if (newFinalizers == null) {
|
||||||
|
newFinalizers = new HashSet<>();
|
||||||
|
tag.getMetadata().setFinalizers(newFinalizers);
|
||||||
|
}
|
||||||
|
newFinalizers.add(FINALIZER_NAME);
|
||||||
|
client.update(tag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
client.update(tag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reconcileStatus(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);
|
tagPermalinkPolicy.onPermalinkAdd(tag);
|
||||||
|
|
||||||
|
if (!oldTag.equals(tag)) {
|
||||||
|
client.update(tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDeleted(Tag tag) {
|
||||||
|
return tag.getMetadata().getDeletionTimestamp() != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.theme.router;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -52,6 +53,9 @@ public class PermalinkIndexer {
|
||||||
List<String> permalinks = permalinkLookup.get(locator.gvk());
|
List<String> permalinks = permalinkLookup.get(locator.gvk());
|
||||||
if (permalinks != null) {
|
if (permalinks != null) {
|
||||||
permalinks.remove(permalink);
|
permalinks.remove(permalink);
|
||||||
|
if (permalinks.isEmpty()) {
|
||||||
|
permalinkLookup.remove(locator.gvk());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
permalinkLocatorMap.remove(permalink);
|
permalinkLocatorMap.remove(permalink);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -83,7 +87,7 @@ public class PermalinkIndexer {
|
||||||
public List<String> getPermalinks(GroupVersionKind gvk) {
|
public List<String> getPermalinks(GroupVersionKind gvk) {
|
||||||
readWriteLock.readLock().lock();
|
readWriteLock.readLock().lock();
|
||||||
try {
|
try {
|
||||||
return permalinkLookup.get(gvk);
|
return Objects.requireNonNullElse(permalinkLookup.get(gvk), List.of());
|
||||||
} finally {
|
} finally {
|
||||||
readWriteLock.readLock().unlock();
|
readWriteLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
|
@ -196,7 +200,7 @@ public class PermalinkIndexer {
|
||||||
|
|
||||||
@EventListener(PermalinkIndexDeleteCommand.class)
|
@EventListener(PermalinkIndexDeleteCommand.class)
|
||||||
public void onPermalinkDelete(PermalinkIndexDeleteCommand deleteCommand) {
|
public void onPermalinkDelete(PermalinkIndexDeleteCommand deleteCommand) {
|
||||||
register(deleteCommand.getLocator(), deleteCommand.getPermalink());
|
remove(deleteCommand.getLocator(), deleteCommand.getPermalink());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventListener(PermalinkIndexUpdateCommand.class)
|
@EventListener(PermalinkIndexUpdateCommand.class)
|
||||||
|
|
|
@ -10,10 +10,10 @@ import static org.mockito.Mockito.when;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -43,17 +43,14 @@ class PostReconcilerTest {
|
||||||
@Mock
|
@Mock
|
||||||
private PostPermalinkPolicy postPermalinkPolicy;
|
private PostPermalinkPolicy postPermalinkPolicy;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
private PostReconciler postReconciler;
|
private PostReconciler postReconciler;
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
postReconciler = new PostReconciler(client, contentService, postPermalinkPolicy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reconcile() {
|
void reconcile() {
|
||||||
String name = "post-A";
|
String name = "post-A";
|
||||||
Post post = TestPost.postV1();
|
Post post = TestPost.postV1();
|
||||||
|
post.getSpec().setPublished(false);
|
||||||
post.getSpec().setHeadSnapshot("post-A-head-snapshot");
|
post.getSpec().setHeadSnapshot("post-A-head-snapshot");
|
||||||
when(client.fetch(eq(Post.class), eq(name)))
|
when(client.fetch(eq(Post.class), eq(name)))
|
||||||
.thenReturn(Optional.of(post));
|
.thenReturn(Optional.of(post));
|
||||||
|
@ -72,7 +69,12 @@ class PostReconcilerTest {
|
||||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||||
postReconciler.reconcile(new Reconciler.Request(name));
|
postReconciler.reconcile(new Reconciler.Request(name));
|
||||||
|
|
||||||
verify(client, times(1)).update(captor.capture());
|
verify(client, times(3)).update(captor.capture());
|
||||||
|
|
||||||
|
verify(postPermalinkPolicy, times(1)).permalink(any());
|
||||||
|
verify(postPermalinkPolicy, times(0)).onPermalinkAdd(any());
|
||||||
|
verify(postPermalinkPolicy, times(1)).onPermalinkDelete(any());
|
||||||
|
verify(postPermalinkPolicy, times(0)).onPermalinkUpdate(any());
|
||||||
|
|
||||||
Post value = captor.getValue();
|
Post value = captor.getValue();
|
||||||
assertThat(value.getStatus().getExcerpt()).isEqualTo("hello world");
|
assertThat(value.getStatus().getExcerpt()).isEqualTo("hello world");
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package run.halo.app.core.extension.reconciler;
|
||||||
|
|
||||||
|
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.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import run.halo.app.content.permalinks.TagPermalinkPolicy;
|
||||||
|
import run.halo.app.core.extension.Tag;
|
||||||
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
import run.halo.app.extension.Metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link TagReconciler}.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TagReconcilerTest {
|
||||||
|
@Mock
|
||||||
|
private ExtensionClient client;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TagPermalinkPolicy tagPermalinkPolicy;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private TagReconciler tagReconciler;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reconcile() {
|
||||||
|
Tag tag = tag();
|
||||||
|
when(client.fetch(eq(Tag.class), eq("fake-tag")))
|
||||||
|
.thenReturn(Optional.of(tag));
|
||||||
|
when(tagPermalinkPolicy.permalink(any()))
|
||||||
|
.thenAnswer(arg -> "/tags/" + tag.getSpec().getSlug());
|
||||||
|
ArgumentCaptor<Tag> captor = ArgumentCaptor.forClass(Tag.class);
|
||||||
|
|
||||||
|
tagReconciler.reconcile(new TagReconciler.Request("fake-tag"));
|
||||||
|
|
||||||
|
verify(client, times(2)).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(3)).update(captor.capture());
|
||||||
|
verify(tagPermalinkPolicy, times(2)).onPermalinkAdd(any());
|
||||||
|
verify(tagPermalinkPolicy, times(2)).onPermalinkDelete(any());
|
||||||
|
assertThat(capture.getStatus().getPermalink()).isEqualTo("/tags/new-slug");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reconcileDelete() {
|
||||||
|
Tag tag = tag();
|
||||||
|
tag.getMetadata().setDeletionTimestamp(Instant.now());
|
||||||
|
when(client.fetch(eq(Tag.class), eq("fake-tag")))
|
||||||
|
.thenReturn(Optional.of(tag));
|
||||||
|
ArgumentCaptor<Tag> captor = ArgumentCaptor.forClass(Tag.class);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag tag() {
|
||||||
|
Tag tag = new Tag();
|
||||||
|
tag.setMetadata(new Metadata());
|
||||||
|
tag.getMetadata().setName("fake-tag");
|
||||||
|
|
||||||
|
tag.setSpec(new Tag.TagSpec());
|
||||||
|
tag.getSpec().setSlug("fake-slug");
|
||||||
|
|
||||||
|
tag.setStatus(new Tag.TagStatus());
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue