mirror of https://github.com/halo-dev/halo
feat: add the lastModifyTime attribute for post and single page (#3101)
#### What type of PR is this? /kind improvement /milestone 2.2.x #### What this PR does / why we need it: 文章和自定义页面支持展示最后修改时间 - 文章发布后会同步 post.spec.releasedSnapshot 记录的最后修改时间到 post.status.lastModifyTime,自定义页面亦如是。 - 主题端可以通过 `${item.status.lastModifyTime}` 获取(item表示文章或自定义页面 Vo)。 #### Which issue(s) this PR fixes: Fixes #3090 #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note 文章和自定义页面支持展示最后修改时间 ```pull/3102/head^2
parent
4533a83c0a
commit
9740de8d4a
|
@ -150,6 +150,8 @@ public class Post extends AbstractExtension {
|
|||
|
||||
private List<String> contributors;
|
||||
|
||||
private Instant lastModifyTime;
|
||||
|
||||
@JsonIgnore
|
||||
public ConditionList getConditionsOrDefault() {
|
||||
if (this.conditions == null) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -13,7 +14,6 @@ import org.springframework.context.ApplicationContext;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.content.ContentService;
|
||||
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.Post;
|
||||
|
@ -53,7 +53,6 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
private static final String FINALIZER_NAME = "post-protection";
|
||||
private final ExtensionClient client;
|
||||
private final ContentService contentService;
|
||||
private final PostService postService;
|
||||
private final PostPermalinkPolicy postPermalinkPolicy;
|
||||
private final CounterService counterService;
|
||||
|
||||
|
@ -126,9 +125,9 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
Post.PostStatus status = post.getStatusOrDefault();
|
||||
|
||||
// validate release snapshot
|
||||
boolean present = client.fetch(Snapshot.class, releaseSnapshot)
|
||||
.isPresent();
|
||||
if (!present) {
|
||||
Optional<Snapshot> releasedSnapshotOpt =
|
||||
client.fetch(Snapshot.class, releaseSnapshot);
|
||||
if (releasedSnapshotOpt.isEmpty()) {
|
||||
Condition condition = Condition.builder()
|
||||
.type(Post.PostPhase.FAILED.name())
|
||||
.reason("SnapshotNotFound")
|
||||
|
@ -159,6 +158,9 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
post.getSpec().setPublishTime(Instant.now());
|
||||
}
|
||||
|
||||
// populate lastModifyTime
|
||||
status.setLastModifyTime(releasedSnapshotOpt.get().getSpec().getLastModifyTime());
|
||||
|
||||
client.update(post);
|
||||
applicationContext.publishEvent(new PostPublishedEvent(this, name));
|
||||
});
|
||||
|
@ -301,6 +303,12 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
!StringUtils.equals(headSnapshot, post.getSpec().getReleaseSnapshot()));
|
||||
});
|
||||
|
||||
if (post.isPublished() && status.getLastModifyTime() == null) {
|
||||
client.fetch(Snapshot.class, post.getSpec().getReleaseSnapshot())
|
||||
.ifPresent(releasedSnapshot ->
|
||||
status.setLastModifyTime(releasedSnapshot.getSpec().getLastModifyTime()));
|
||||
}
|
||||
|
||||
if (!oldPost.equals(post)) {
|
||||
client.update(post);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -127,9 +128,9 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
SinglePage.SinglePageStatus status = page.getStatusOrDefault();
|
||||
|
||||
// validate release snapshot
|
||||
boolean present = client.fetch(Snapshot.class, releaseSnapshot)
|
||||
.isPresent();
|
||||
if (!present) {
|
||||
Optional<Snapshot> releasedSnapshotOpt =
|
||||
client.fetch(Snapshot.class, releaseSnapshot);
|
||||
if (releasedSnapshotOpt.isEmpty()) {
|
||||
Condition condition = Condition.builder()
|
||||
.type(Post.PostPhase.FAILED.name())
|
||||
.reason("SnapshotNotFound")
|
||||
|
@ -161,6 +162,9 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
page.getSpec().setPublishTime(Instant.now());
|
||||
}
|
||||
|
||||
// populate lastModifyTime
|
||||
status.setLastModifyTime(releasedSnapshotOpt.get().getSpec().getLastModifyTime());
|
||||
|
||||
client.update(page);
|
||||
});
|
||||
}
|
||||
|
@ -365,6 +369,12 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
status.setInProgress(!StringUtils.equals(releaseSnapshot, headSnapshot));
|
||||
});
|
||||
|
||||
if (singlePage.isPublished() && status.getLastModifyTime() == null) {
|
||||
client.fetch(Snapshot.class, singlePage.getSpec().getReleaseSnapshot())
|
||||
.ifPresent(releasedSnapshot ->
|
||||
status.setLastModifyTime(releasedSnapshot.getSpec().getLastModifyTime()));
|
||||
}
|
||||
|
||||
if (!oldPage.equals(singlePage)) {
|
||||
client.update(singlePage);
|
||||
}
|
||||
|
|
|
@ -7,16 +7,19 @@ 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
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 org.springframework.context.ApplicationContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentService;
|
||||
|
@ -26,6 +29,7 @@ import run.halo.app.content.TestPost;
|
|||
import run.halo.app.content.permalinks.PostPermalinkPolicy;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.core.extension.content.Snapshot;
|
||||
import run.halo.app.event.post.PostPublishedEvent;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
|
||||
|
@ -48,6 +52,9 @@ class PostReconcilerTest {
|
|||
@Mock
|
||||
private PostService postService;
|
||||
|
||||
@Mock
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@InjectMocks
|
||||
private PostReconciler postReconciler;
|
||||
|
||||
|
@ -119,4 +126,68 @@ class PostReconcilerTest {
|
|||
Post value = captor.getValue();
|
||||
assertThat(value.getStatus().getExcerpt()).isEqualTo("hello world");
|
||||
}
|
||||
|
||||
@Nested
|
||||
class LastModifyTimeTest {
|
||||
@Test
|
||||
void reconcileLastModifyTimeWhenPostIsPublished() {
|
||||
String name = "post-A";
|
||||
Post post = TestPost.postV1();
|
||||
post.getSpec().setPublish(true);
|
||||
post.getSpec().setHeadSnapshot("post-A-head-snapshot");
|
||||
post.getSpec().setReleaseSnapshot("post-fake-released-snapshot");
|
||||
when(client.fetch(eq(Post.class), eq(name)))
|
||||
.thenReturn(Optional.of(post));
|
||||
when(contentService.getContent(eq(post.getSpec().getReleaseSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(post.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
.content("<p>hello world</p>")
|
||||
.rawType("markdown")
|
||||
.build()));
|
||||
Instant lastModifyTime = Instant.now();
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.getSpec().setLastModifyTime(lastModifyTime);
|
||||
when(client.fetch(eq(Snapshot.class), eq(post.getSpec().getReleaseSnapshot())))
|
||||
.thenReturn(Optional.of(snapshotV2));
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.empty());
|
||||
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
postReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
||||
verify(client, times(4)).update(captor.capture());
|
||||
Post value = captor.getValue();
|
||||
assertThat(value.getStatus().getLastModifyTime()).isEqualTo(lastModifyTime);
|
||||
verify(applicationContext).publishEvent(any(PostPublishedEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconcileLastModifyTimeWhenPostIsNotPublished() {
|
||||
String name = "post-A";
|
||||
Post post = TestPost.postV1();
|
||||
post.getSpec().setPublish(false);
|
||||
post.getSpec().setHeadSnapshot("post-A-head-snapshot");
|
||||
when(client.fetch(eq(Post.class), eq(name)))
|
||||
.thenReturn(Optional.of(post));
|
||||
when(contentService.getContent(eq(post.getSpec().getReleaseSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(post.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
.content("<p>hello world</p>")
|
||||
.rawType("markdown")
|
||||
.build()));
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.empty());
|
||||
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
postReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
||||
verify(client, times(3)).update(captor.capture());
|
||||
Post value = captor.getValue();
|
||||
assertThat(value.getStatus().getLastModifyTime()).isNull();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,9 +10,11 @@ import static org.mockito.Mockito.when;
|
|||
import static run.halo.app.content.TestPost.snapshotV1;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
@ -123,6 +125,74 @@ class SinglePageReconcilerTest {
|
|||
assertThat(permalink).isEqualTo("http://example.com/%E4%B8%AD%E6%96%87%20slug");
|
||||
}
|
||||
|
||||
@Nested
|
||||
class LastModifyTimeTest {
|
||||
@Test
|
||||
void reconcileLastModifyTimeWhenPageIsPublished() {
|
||||
String name = "page-A";
|
||||
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
|
||||
|
||||
SinglePage page = pageV1();
|
||||
page.getSpec().setPublish(true);
|
||||
page.getSpec().setHeadSnapshot("page-A-head-snapshot");
|
||||
page.getSpec().setReleaseSnapshot("page-fake-released-snapshot");
|
||||
when(client.fetch(eq(SinglePage.class), eq(name)))
|
||||
.thenReturn(Optional.of(page));
|
||||
when(contentService.getContent(eq(page.getSpec().getHeadSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(page.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
.content("<p>hello world</p>")
|
||||
.rawType("markdown")
|
||||
.build())
|
||||
);
|
||||
Instant lastModifyTime = Instant.now();
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.getSpec().setLastModifyTime(lastModifyTime);
|
||||
when(client.fetch(eq(Snapshot.class), eq(page.getSpec().getReleaseSnapshot())))
|
||||
.thenReturn(Optional.of(snapshotV2));
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.empty());
|
||||
|
||||
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
||||
singlePageReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
||||
verify(client, times(4)).update(captor.capture());
|
||||
SinglePage value = captor.getValue();
|
||||
assertThat(value.getStatus().getLastModifyTime()).isEqualTo(lastModifyTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconcileLastModifyTimeWhenPageIsNotPublished() {
|
||||
String name = "page-A";
|
||||
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
|
||||
|
||||
SinglePage page = pageV1();
|
||||
page.getSpec().setPublish(false);
|
||||
when(client.fetch(eq(SinglePage.class), eq(name)))
|
||||
.thenReturn(Optional.of(page));
|
||||
when(contentService.getContent(eq(page.getSpec().getHeadSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(page.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
.content("<p>hello world</p>")
|
||||
.rawType("markdown")
|
||||
.build())
|
||||
);
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.empty());
|
||||
|
||||
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
||||
singlePageReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
||||
verify(client, times(3)).update(captor.capture());
|
||||
SinglePage value = captor.getValue();
|
||||
assertThat(value.getStatus().getLastModifyTime()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
public static SinglePage pageV1() {
|
||||
SinglePage page = new SinglePage();
|
||||
page.setKind(Post.KIND);
|
||||
|
|
Loading…
Reference in New Issue