mirror of https://github.com/halo-dev/halo
refactor: change from manual publishing to automatic publishing (#2659)
#### What type of PR is this? /kind improvement /milestone 2.0 /area core /kind api-change #### What this PR does / why we need it: - 通过修改 `spec.publish=true` 且 `spec.releasedSnapshot = spec.headSnapshot` 来实现通过 reconciler 异步发布 - Snapshot 中的 subjectRef 类型改为了 Ref 类型,因为之前的 Snapshot.SubjectRef 只包含了 kind 和 name 可能会冲突 #### Which issue(s) this PR fixes: Fixes #2650 #### Special notes for your reviewer: how to test it? 1. 通过 `POST /apis/console.halo.run/v1alpha1/posts` 创建文章并将 `spec.publish=true`,创建后查询可以看到 `spec.baseSnapshot`、`spec.headSnapshot`、`spec.releasedSnapshot` 三个值都相等,且 `status.phase=PUBLISHED`(此过程相当于创建即发布没有保存过程) 2. 先通过 `POST /apis/console.halo.run/v1alpha1/posts` 创建一篇草稿(`spec.publish=false`),在获取它并设置 `spec.publish=true` ,更新后期望文章为发布状态 `spec.headSnapshot`, `spec.releasedSnapshot` 都不等于空且等于 `spec.baseSnapshot`),且 `spec.version=1`(此过程相当于先保存后发布且之前从未发布过) 3. 在步骤2的基础上修改`spec.releasedSnapshot=spec.headSnapshot`并更新,期望 `spec.version=2`且`spec.releasedSnapshot` 对应的 Snapshot 数据具有 publishTime(此过程相当于发布后编辑内容在发布的场景) 4. 自定义页面亦如是 /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note 重构文章发布以解决创建与发布 API 几乎同时调用时无法成功发布文章的问题 ```pull/2692/head
parent
5c05554bd8
commit
3adf5b8a95
|
@ -8,6 +8,8 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.PostService;
|
||||
import run.halo.app.content.SinglePageService;
|
||||
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
|
||||
import run.halo.app.content.permalinks.PostPermalinkPolicy;
|
||||
import run.halo.app.content.permalinks.TagPermalinkPolicy;
|
||||
|
@ -148,9 +150,11 @@ public class ExtensionConfiguration {
|
|||
|
||||
@Bean
|
||||
Controller postController(ExtensionClient client, ContentService contentService,
|
||||
PostPermalinkPolicy postPermalinkPolicy, CounterService counterService) {
|
||||
PostPermalinkPolicy postPermalinkPolicy, CounterService counterService,
|
||||
PostService postService) {
|
||||
return new ControllerBuilder("post-controller", client)
|
||||
.reconciler(new PostReconciler(client, contentService, postPermalinkPolicy,
|
||||
.reconciler(new PostReconciler(client, contentService, postService,
|
||||
postPermalinkPolicy,
|
||||
counterService))
|
||||
.extension(new Post())
|
||||
.build();
|
||||
|
@ -198,10 +202,10 @@ public class ExtensionConfiguration {
|
|||
@Bean
|
||||
Controller singlePageController(ExtensionClient client, ContentService contentService,
|
||||
ApplicationContext applicationContext, CounterService counterService,
|
||||
ExternalUrlSupplier externalUrlSupplier) {
|
||||
SinglePageService singlePageService, ExternalUrlSupplier externalUrlSupplier) {
|
||||
return new ControllerBuilder("single-page-controller", client)
|
||||
.reconciler(new SinglePageReconciler(client, contentService,
|
||||
applicationContext, counterService, externalUrlSupplier)
|
||||
applicationContext, singlePageService, counterService, externalUrlSupplier)
|
||||
)
|
||||
.extension(new SinglePage())
|
||||
.build();
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public record ContentRequest(@Schema(required = true) Snapshot.SubjectRef subjectRef,
|
||||
public record ContentRequest(@Schema(required = true) Ref subjectRef,
|
||||
String headSnapshotName,
|
||||
@Schema(required = true) String raw,
|
||||
@Schema(required = true) String content,
|
||||
|
@ -25,8 +27,8 @@ public record ContentRequest(@Schema(required = true) Snapshot.SubjectRef subjec
|
|||
snapShotSpec.setSubjectRef(subjectRef);
|
||||
snapShotSpec.setVersion(1);
|
||||
snapShotSpec.setRawType(rawType);
|
||||
snapShotSpec.setRawPatch(raw);
|
||||
snapShotSpec.setContentPatch(content);
|
||||
snapShotSpec.setRawPatch(StringUtils.defaultString(raw()));
|
||||
snapShotSpec.setContentPatch(StringUtils.defaultString(content()));
|
||||
String displayVersion = Snapshot.displayVersionFrom(snapShotSpec.getVersion());
|
||||
snapShotSpec.setDisplayVersion(displayVersion);
|
||||
|
||||
|
@ -34,7 +36,7 @@ public record ContentRequest(@Schema(required = true) Snapshot.SubjectRef subjec
|
|||
return snapshot;
|
||||
}
|
||||
|
||||
private String defaultName(Snapshot.SubjectRef subjectRef) {
|
||||
private String defaultName(Ref subjectRef) {
|
||||
// example: Post-apost-v1-snapshot
|
||||
return String.join("-", subjectRef.getKind(),
|
||||
subjectRef.getName(), "v1", "snapshot");
|
||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.content;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* Content service for {@link Snapshot}.
|
||||
|
@ -18,13 +19,13 @@ public interface ContentService {
|
|||
|
||||
Mono<ContentWrapper> updateContent(ContentRequest content);
|
||||
|
||||
Mono<ContentWrapper> publish(String headSnapshotName, Snapshot.SubjectRef subjectRef);
|
||||
Mono<ContentWrapper> publish(String headSnapshotName, Ref subjectRef);
|
||||
|
||||
Mono<Snapshot> getBaseSnapshot(Snapshot.SubjectRef subjectRef);
|
||||
Mono<Snapshot> getBaseSnapshot(Ref subjectRef);
|
||||
|
||||
Mono<Snapshot> latestSnapshotVersion(Snapshot.SubjectRef subjectRef);
|
||||
Mono<Snapshot> latestSnapshotVersion(Ref subjectRef);
|
||||
|
||||
Mono<Snapshot> latestPublishedSnapshot(Snapshot.SubjectRef subjectRef);
|
||||
Mono<Snapshot> latestPublishedSnapshot(Ref subjectRef);
|
||||
|
||||
Flux<Snapshot> listSnapshots(Snapshot.SubjectRef subjectRef);
|
||||
Flux<Snapshot> listSnapshots(Ref subjectRef);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public record ContentWrapper(@Schema(required = true) String snapshotName,
|
||||
@Schema(required = true) String raw,
|
||||
@Schema(required = true) String content,
|
||||
@Schema(required = true) String rawType) {
|
||||
@Data
|
||||
@Builder
|
||||
public class ContentWrapper {
|
||||
private String snapshotName;
|
||||
private Integer version;
|
||||
private String raw;
|
||||
private String content;
|
||||
private String rawType;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ public class ListedPost {
|
|||
@Schema(required = true)
|
||||
private List<Contributor> contributors;
|
||||
|
||||
@Schema(required = true)
|
||||
private Contributor owner;
|
||||
|
||||
@Schema(required = true)
|
||||
private Stats stats;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ public class ListedSinglePage {
|
|||
@Schema(required = true)
|
||||
private List<Contributor> contributors;
|
||||
|
||||
@Schema(required = true)
|
||||
private Contributor owner;
|
||||
|
||||
@Schema(required = true)
|
||||
private Stats stats;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package run.halo.app.content;
|
|||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
|
@ -12,8 +12,7 @@ public record PostRequest(@Schema(required = true) Post post,
|
|||
@Schema(required = true) Content content) {
|
||||
|
||||
public ContentRequest contentRequest() {
|
||||
Snapshot.SubjectRef subjectRef =
|
||||
Snapshot.SubjectRef.of(Post.KIND, post.getMetadata().getName());
|
||||
Ref subjectRef = Ref.of(post);
|
||||
return new ContentRequest(subjectRef, post.getSpec().getHeadSnapshot(), content.raw,
|
||||
content.content, content.rawType);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package run.halo.app.content;
|
|||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import run.halo.app.core.extension.SinglePage;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* A request parameter for {@link SinglePage}.
|
||||
|
@ -14,8 +14,7 @@ public record SinglePageRequest(@Schema(required = true) SinglePage page,
|
|||
@Schema(required = true) Content content) {
|
||||
|
||||
public ContentRequest contentRequest() {
|
||||
Snapshot.SubjectRef subjectRef =
|
||||
Snapshot.SubjectRef.of(SinglePage.KIND, page.getMetadata().getName());
|
||||
Ref subjectRef = Ref.of(page);
|
||||
return new ContentRequest(subjectRef, page.getSpec().getHeadSnapshot(), content.raw,
|
||||
content.content, content.rawType);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.content.impl;
|
|||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
|
@ -15,7 +16,9 @@ import run.halo.app.content.ContentRequest;
|
|||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* A default implementation of {@link ContentService}.
|
||||
|
@ -65,15 +68,11 @@ public class ContentServiceImpl implements ContentService {
|
|||
Snapshot headSnapShot = tuple.getT2();
|
||||
return handleSnapshot(headSnapShot, contentRequest, username);
|
||||
})
|
||||
.flatMap(snapshot -> restoredContent(snapshot)
|
||||
.map(content -> new ContentWrapper(snapshot.getMetadata().getName(),
|
||||
content.raw(), content.content(), content.rawType())
|
||||
)
|
||||
);
|
||||
.flatMap(this::restoredContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ContentWrapper> publish(String headSnapshotName, Snapshot.SubjectRef subjectRef) {
|
||||
public Mono<ContentWrapper> publish(String headSnapshotName, Ref subjectRef) {
|
||||
Assert.notNull(headSnapshotName, "The headSnapshotName must not be null");
|
||||
return client.fetch(Snapshot.class, headSnapshotName)
|
||||
.flatMap(snapshot -> {
|
||||
|
@ -82,7 +81,8 @@ public class ContentServiceImpl implements ContentService {
|
|||
return restoredContent(snapshot.getMetadata().getName(),
|
||||
subjectRef);
|
||||
}
|
||||
|
||||
Map<String, String> labels = ExtensionUtil.nullSafeLabels(snapshot);
|
||||
Snapshot.putPublishedLabel(labels);
|
||||
Snapshot.SnapShotSpec snapshotSpec = snapshot.getSpec();
|
||||
snapshotSpec.setPublishTime(Instant.now());
|
||||
snapshotSpec.setDisplayVersion(
|
||||
|
@ -95,7 +95,7 @@ public class ContentServiceImpl implements ContentService {
|
|||
}
|
||||
|
||||
private Mono<ContentWrapper> restoredContent(String snapshotName,
|
||||
Snapshot.SubjectRef subjectRef) {
|
||||
Ref subjectRef) {
|
||||
return getBaseSnapshot(subjectRef)
|
||||
.flatMap(baseSnapshot -> client.fetch(Snapshot.class, snapshotName)
|
||||
.map(snapshot -> snapshot.applyPatch(baseSnapshot)));
|
||||
|
@ -107,7 +107,7 @@ public class ContentServiceImpl implements ContentService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Snapshot> getBaseSnapshot(Snapshot.SubjectRef subjectRef) {
|
||||
public Mono<Snapshot> getBaseSnapshot(Ref subjectRef) {
|
||||
return listSnapshots(subjectRef)
|
||||
.filter(snapshot -> snapshot.getSpec().getVersion() == 1)
|
||||
.next();
|
||||
|
@ -115,7 +115,7 @@ public class ContentServiceImpl implements ContentService {
|
|||
|
||||
private Mono<Snapshot> handleSnapshot(Snapshot headSnapshot, ContentRequest contentRequest,
|
||||
String username) {
|
||||
Snapshot.SubjectRef subjectRef = contentRequest.subjectRef();
|
||||
Ref subjectRef = contentRequest.subjectRef();
|
||||
return getBaseSnapshot(subjectRef).flatMap(baseSnapshot -> {
|
||||
String baseSnapshotName = baseSnapshot.getMetadata().getName();
|
||||
return latestPublishedSnapshot(subjectRef)
|
||||
|
@ -142,6 +142,14 @@ public class ContentServiceImpl implements ContentService {
|
|||
latestReleasedSnapshot.getMetadata().getName();
|
||||
if (headSnapshot.isPublished() || StringUtils.equals(headSnapshotName,
|
||||
releasedSnapshotName)) {
|
||||
String latestSnapshotName = latestSnapshot.getMetadata().getName();
|
||||
if (!headSnapshotName.equals(latestSnapshotName)
|
||||
&& !latestSnapshot.isPublished()) {
|
||||
// publish it then create new one
|
||||
return publish(latestSnapshotName, subjectRef)
|
||||
.then(createNewSnapshot(newSnapshot, baseSnapshotName,
|
||||
contentRequest));
|
||||
}
|
||||
// create a new snapshot,done
|
||||
return createNewSnapshot(newSnapshot, baseSnapshotName,
|
||||
contentRequest);
|
||||
|
@ -160,7 +168,7 @@ public class ContentServiceImpl implements ContentService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Snapshot> latestSnapshotVersion(Snapshot.SubjectRef subjectRef) {
|
||||
public Mono<Snapshot> latestSnapshotVersion(Ref subjectRef) {
|
||||
Assert.notNull(subjectRef, "The subjectRef must not be null.");
|
||||
return listSnapshots(subjectRef)
|
||||
.sort(LATEST_SNAPSHOT_COMPARATOR)
|
||||
|
@ -168,7 +176,7 @@ public class ContentServiceImpl implements ContentService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<Snapshot> latestPublishedSnapshot(Snapshot.SubjectRef subjectRef) {
|
||||
public Mono<Snapshot> latestPublishedSnapshot(Ref subjectRef) {
|
||||
Assert.notNull(subjectRef, "The subjectRef must not be null.");
|
||||
return listSnapshots(subjectRef)
|
||||
.filter(Snapshot::isPublished)
|
||||
|
@ -177,7 +185,7 @@ public class ContentServiceImpl implements ContentService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Flux<Snapshot> listSnapshots(Snapshot.SubjectRef subjectRef) {
|
||||
public Flux<Snapshot> listSnapshots(Ref subjectRef) {
|
||||
Assert.notNull(subjectRef, "The subjectRef must not be null.");
|
||||
return client.list(Snapshot.class, snapshot -> subjectRef.equals(snapshot.getSpec()
|
||||
.getSubjectRef()), null);
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
|
@ -36,8 +37,11 @@ import run.halo.app.core.extension.Tag;
|
|||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.infra.Condition;
|
||||
import run.halo.app.infra.ConditionStatus;
|
||||
import run.halo.app.infra.exception.NotFoundException;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
|
||||
|
@ -47,6 +51,7 @@ import run.halo.app.metrics.MeterUtils;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PostServiceImpl implements PostService {
|
||||
private final ContentService contentService;
|
||||
|
@ -160,7 +165,8 @@ public class PostServiceImpl implements PostService {
|
|||
})
|
||||
.flatMap(lp -> setTags(post.getSpec().getTags(), lp))
|
||||
.flatMap(lp -> setCategories(post.getSpec().getCategories(), lp))
|
||||
.flatMap(lp -> setContributors(post.getStatus().getContributors(), lp));
|
||||
.flatMap(lp -> setContributors(post.getStatus().getContributors(), lp))
|
||||
.flatMap(lp -> setOwner(post.getSpec().getOwner(), lp));
|
||||
}
|
||||
|
||||
private Mono<ListedPost> setTags(List<String> tagNames, ListedPost post) {
|
||||
|
@ -187,6 +193,19 @@ public class PostServiceImpl implements PostService {
|
|||
.switchIfEmpty(Mono.defer(() -> Mono.just(post)));
|
||||
}
|
||||
|
||||
private Mono<ListedPost> setOwner(String ownerName, ListedPost post) {
|
||||
return client.fetch(User.class, ownerName)
|
||||
.map(user -> {
|
||||
Contributor contributor = new Contributor();
|
||||
contributor.setName(user.getMetadata().getName());
|
||||
contributor.setDisplayName(user.getSpec().getDisplayName());
|
||||
contributor.setAvatar(user.getSpec().getAvatar());
|
||||
return contributor;
|
||||
})
|
||||
.doOnNext(post::setOwner)
|
||||
.thenReturn(post);
|
||||
}
|
||||
|
||||
private Flux<Tag> listTags(List<String> tagNames) {
|
||||
if (tagNames == null) {
|
||||
return Flux.empty();
|
||||
|
@ -224,8 +243,8 @@ public class PostServiceImpl implements PostService {
|
|||
.flatMap(contentWrapper -> getContextUsername()
|
||||
.flatMap(username -> {
|
||||
Post post = postRequest.post();
|
||||
post.getSpec().setBaseSnapshot(contentWrapper.snapshotName());
|
||||
post.getSpec().setHeadSnapshot(contentWrapper.snapshotName());
|
||||
post.getSpec().setBaseSnapshot(contentWrapper.getSnapshotName());
|
||||
post.getSpec().setHeadSnapshot(contentWrapper.getSnapshotName());
|
||||
post.getSpec().setOwner(username);
|
||||
appendPublishedCondition(post, Post.PostPhase.DRAFT);
|
||||
return client.create(post)
|
||||
|
@ -239,7 +258,7 @@ public class PostServiceImpl implements PostService {
|
|||
Post post = postRequest.post();
|
||||
return contentService.updateContent(postRequest.contentRequest())
|
||||
.flatMap(contentWrapper -> {
|
||||
post.getSpec().setHeadSnapshot(contentWrapper.snapshotName());
|
||||
post.getSpec().setHeadSnapshot(contentWrapper.getSnapshotName());
|
||||
return client.update(post);
|
||||
})
|
||||
.retryWhen(Retry.backoff(5, Duration.ofMillis(100))
|
||||
|
@ -256,43 +275,45 @@ public class PostServiceImpl implements PostService {
|
|||
@Override
|
||||
public Mono<Post> publishPost(String postName) {
|
||||
return client.fetch(Post.class, postName)
|
||||
.filter(post -> Objects.equals(true, post.getSpec().getPublish()))
|
||||
.flatMap(post -> {
|
||||
Post.PostSpec spec = post.getSpec();
|
||||
// publish snapshot
|
||||
return Mono.zip(Mono.just(post),
|
||||
client.fetch(Snapshot.class, spec.getHeadSnapshot()));
|
||||
})
|
||||
.flatMap(tuple -> {
|
||||
Post post = tuple.getT1();
|
||||
Snapshot snapshot = tuple.getT2();
|
||||
|
||||
Post.PostSpec postSpec = post.getSpec();
|
||||
if (Objects.equals(true, postSpec.getPublished())) {
|
||||
// has been published before
|
||||
postSpec.setVersion(postSpec.getVersion() + 1);
|
||||
} else {
|
||||
postSpec.setPublished(true);
|
||||
final Post oldPost = JsonUtils.deepCopy(post);
|
||||
final Post.PostSpec postSpec = post.getSpec();
|
||||
// if it's published state but releaseSnapshot is null, it means that need to
|
||||
// publish headSnapshot
|
||||
// if releaseSnapshot is draft and publish state is true, it means that need to
|
||||
// publish releaseSnapshot
|
||||
if (StringUtils.isBlank(postSpec.getHeadSnapshot())) {
|
||||
postSpec.setHeadSnapshot(postSpec.getBaseSnapshot());
|
||||
}
|
||||
|
||||
if (postSpec.getPublishTime() == null) {
|
||||
postSpec.setPublishTime(Instant.now());
|
||||
if (StringUtils.isBlank(postSpec.getReleaseSnapshot())) {
|
||||
postSpec.setReleaseSnapshot(postSpec.getHeadSnapshot());
|
||||
postSpec.setVersion(0);
|
||||
}
|
||||
|
||||
// update release snapshot name and condition
|
||||
postSpec.setReleaseSnapshot(snapshot.getMetadata().getName());
|
||||
appendPublishedCondition(post, Post.PostPhase.PUBLISHED);
|
||||
|
||||
Snapshot.SubjectRef subjectRef =
|
||||
Snapshot.SubjectRef.of(Post.KIND, post.getMetadata().getName());
|
||||
return contentService.publish(snapshot.getMetadata().getName(), subjectRef)
|
||||
.flatMap(contentWrapper -> {
|
||||
post.getSpec().setReleaseSnapshot(contentWrapper.snapshotName());
|
||||
return client.update(post);
|
||||
return client.fetch(Snapshot.class, postSpec.getReleaseSnapshot())
|
||||
.flatMap(releasedSnapshot -> {
|
||||
Ref ref = Ref.of(post);
|
||||
// not published state, need to publish
|
||||
return contentService.publish(releasedSnapshot.getMetadata().getName(),
|
||||
ref)
|
||||
.flatMap(contentWrapper -> {
|
||||
appendPublishedCondition(post, Post.PostPhase.PUBLISHED);
|
||||
postSpec.setVersion(contentWrapper.getVersion());
|
||||
Post.changePublishedState(post, true);
|
||||
if (postSpec.getPublishTime() == null) {
|
||||
postSpec.setPublishTime(Instant.now());
|
||||
}
|
||||
if (!oldPost.equals(post)) {
|
||||
return client.update(post);
|
||||
}
|
||||
return Mono.just(post);
|
||||
});
|
||||
})
|
||||
.then(Mono.defer(() -> client.fetch(Post.class, postName)));
|
||||
})
|
||||
.retryWhen(Retry.backoff(5, Duration.ofMillis(100))
|
||||
.filter(throwable -> throwable instanceof OptimisticLockingFailureException));
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new NotFoundException(
|
||||
String.format("Snapshot [%s] not found", postSpec.getReleaseSnapshot()))))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void appendPublishedCondition(Post post, Post.PostPhase phase) {
|
||||
|
@ -300,13 +321,16 @@ public class PostServiceImpl implements PostService {
|
|||
Post.PostStatus status = post.getStatusOrDefault();
|
||||
status.setPhase(phase.name());
|
||||
List<Condition> conditions = status.getConditionsOrDefault();
|
||||
Condition condition = new Condition();
|
||||
conditions.add(condition);
|
||||
conditions.add(createCondition(phase));
|
||||
}
|
||||
|
||||
Condition createCondition(Post.PostPhase phase) {
|
||||
Condition condition = new Condition();
|
||||
condition.setType(phase.name());
|
||||
condition.setReason(phase.name());
|
||||
condition.setMessage("");
|
||||
condition.setStatus(ConditionStatus.TRUE);
|
||||
condition.setLastTransitionTime(Instant.now());
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
|
@ -35,8 +36,11 @@ import run.halo.app.core.extension.Snapshot;
|
|||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.infra.Condition;
|
||||
import run.halo.app.infra.ConditionStatus;
|
||||
import run.halo.app.infra.exception.NotFoundException;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
|
||||
|
@ -46,6 +50,7 @@ import run.halo.app.metrics.MeterUtils;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SinglePageServiceImpl implements SinglePageService {
|
||||
private final ContentService contentService;
|
||||
|
@ -85,8 +90,8 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
.flatMap(contentWrapper -> getContextUsername()
|
||||
.flatMap(username -> {
|
||||
SinglePage page = pageRequest.page();
|
||||
page.getSpec().setBaseSnapshot(contentWrapper.snapshotName());
|
||||
page.getSpec().setHeadSnapshot(contentWrapper.snapshotName());
|
||||
page.getSpec().setBaseSnapshot(contentWrapper.getSnapshotName());
|
||||
page.getSpec().setHeadSnapshot(contentWrapper.getSnapshotName());
|
||||
page.getSpec().setOwner(username);
|
||||
appendPublishedCondition(page, Post.PostPhase.DRAFT);
|
||||
return client.create(page)
|
||||
|
@ -101,7 +106,7 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
SinglePage page = pageRequest.page();
|
||||
return contentService.updateContent(pageRequest.contentRequest())
|
||||
.flatMap(contentWrapper -> {
|
||||
page.getSpec().setHeadSnapshot(contentWrapper.snapshotName());
|
||||
page.getSpec().setHeadSnapshot(contentWrapper.getSnapshotName());
|
||||
return client.update(page);
|
||||
})
|
||||
.retryWhen(Retry.backoff(5, Duration.ofMillis(100))
|
||||
|
@ -112,32 +117,46 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
@Override
|
||||
public Mono<SinglePage> publish(String name) {
|
||||
return client.fetch(SinglePage.class, name)
|
||||
.filter(page -> Objects.equals(true, page.getSpec().getPublish()))
|
||||
.flatMap(page -> {
|
||||
SinglePage.SinglePageSpec spec = page.getSpec();
|
||||
if (Objects.equals(true, spec.getPublished())) {
|
||||
// has been published before
|
||||
spec.setVersion(spec.getVersion() + 1);
|
||||
} else {
|
||||
spec.setPublished(true);
|
||||
final SinglePage oldPage = JsonUtils.deepCopy(page);
|
||||
final SinglePage.SinglePageSpec spec = page.getSpec();
|
||||
// if it's published state but releaseSnapshot is null, it means that need to
|
||||
// publish headSnapshot
|
||||
// if releaseSnapshot is draft and publish state is true, it means that need to
|
||||
// publish releaseSnapshot
|
||||
if (StringUtils.isBlank(spec.getHeadSnapshot())) {
|
||||
spec.setHeadSnapshot(spec.getBaseSnapshot());
|
||||
}
|
||||
|
||||
if (spec.getPublishTime() == null) {
|
||||
spec.setPublishTime(Instant.now());
|
||||
if (StringUtils.isBlank(spec.getReleaseSnapshot())) {
|
||||
spec.setReleaseSnapshot(spec.getHeadSnapshot());
|
||||
// first-time to publish reset version to 0
|
||||
spec.setVersion(0);
|
||||
}
|
||||
|
||||
Snapshot.SubjectRef subjectRef =
|
||||
Snapshot.SubjectRef.of(SinglePage.KIND, page.getMetadata().getName());
|
||||
return contentService.publish(spec.getHeadSnapshot(), subjectRef)
|
||||
.flatMap(contentWrapper -> {
|
||||
// update release snapshot name and condition
|
||||
appendPublishedCondition(page, Post.PostPhase.PUBLISHED);
|
||||
page.getSpec().setReleaseSnapshot(contentWrapper.snapshotName());
|
||||
return client.update(page);
|
||||
return client.fetch(Snapshot.class, spec.getReleaseSnapshot())
|
||||
.flatMap(releasedSnapshot -> {
|
||||
Ref ref = Ref.of(page);
|
||||
// not published state, need to publish
|
||||
return contentService.publish(releasedSnapshot.getMetadata().getName(),
|
||||
ref)
|
||||
.flatMap(contentWrapper -> {
|
||||
appendPublishedCondition(page, Post.PostPhase.PUBLISHED);
|
||||
spec.setVersion(contentWrapper.getVersion());
|
||||
SinglePage.changePublishedState(page, true);
|
||||
if (spec.getPublishTime() == null) {
|
||||
spec.setPublishTime(Instant.now());
|
||||
}
|
||||
if (!oldPage.equals(page)) {
|
||||
return client.update(page);
|
||||
}
|
||||
return Mono.just(page);
|
||||
});
|
||||
})
|
||||
.then(Mono.defer(() -> client.fetch(SinglePage.class, name)));
|
||||
})
|
||||
.retryWhen(Retry.backoff(5, Duration.ofMillis(100))
|
||||
.filter(throwable -> throwable instanceof OptimisticLockingFailureException));
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new NotFoundException(
|
||||
String.format("Snapshot [%s] not found", spec.getReleaseSnapshot()))))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<String> getContextUsername() {
|
||||
|
@ -198,7 +217,8 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
return listedSinglePage;
|
||||
})
|
||||
.flatMap(lsp ->
|
||||
setContributors(singlePage.getStatusOrDefault().getContributors(), lsp));
|
||||
setContributors(singlePage.getStatusOrDefault().getContributors(), lsp))
|
||||
.flatMap(lsp -> setOwner(singlePage.getSpec().getOwner(), lsp));
|
||||
}
|
||||
|
||||
private Mono<ListedSinglePage> setContributors(List<String> usernames,
|
||||
|
@ -210,6 +230,19 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
.defaultIfEmpty(singlePage);
|
||||
}
|
||||
|
||||
private Mono<ListedSinglePage> setOwner(String ownerName, ListedSinglePage page) {
|
||||
return client.fetch(User.class, ownerName)
|
||||
.map(user -> {
|
||||
Contributor contributor = new Contributor();
|
||||
contributor.setName(user.getMetadata().getName());
|
||||
contributor.setDisplayName(user.getSpec().getDisplayName());
|
||||
contributor.setAvatar(user.getSpec().getAvatar());
|
||||
return contributor;
|
||||
})
|
||||
.doOnNext(page::setOwner)
|
||||
.thenReturn(page);
|
||||
}
|
||||
|
||||
Stats fetchStats(SinglePage singlePage) {
|
||||
Assert.notNull(singlePage, "The singlePage must not be null.");
|
||||
String name = singlePage.getMetadata().getName();
|
||||
|
@ -259,12 +292,16 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
status.setPhase(phase.name());
|
||||
List<Condition> conditions = status.getConditionsOrDefault();
|
||||
Condition condition = new Condition();
|
||||
conditions.add(condition);
|
||||
conditions.add(createCondition(phase));
|
||||
}
|
||||
|
||||
Condition createCondition(Post.PostPhase phase) {
|
||||
Condition condition = new Condition();
|
||||
condition.setType(phase.name());
|
||||
condition.setReason(phase.name());
|
||||
condition.setMessage("");
|
||||
condition.setStatus(ConditionStatus.TRUE);
|
||||
condition.setLastTransitionTime(Instant.now());
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.GVK;
|
||||
import run.halo.app.infra.Condition;
|
||||
|
||||
|
@ -31,9 +32,9 @@ public class Post extends AbstractExtension {
|
|||
public static final String CATEGORIES_ANNO = "content.halo.run/categories";
|
||||
public static final String TAGS_ANNO = "content.halo.run/tags";
|
||||
public static final String DELETED_LABEL = "content.halo.run/deleted";
|
||||
public static final String PUBLISHED_LABEL = "content.halo.run/published";
|
||||
public static final String OWNER_LABEL = "content.halo.run/owner";
|
||||
public static final String VISIBLE_LABEL = "content.halo.run/visible";
|
||||
public static final String PHASE_LABEL = "content.halo.run/phase";
|
||||
|
||||
public static final String ARCHIVE_YEAR_LABEL = "content.halo.run/archive-year";
|
||||
|
||||
|
@ -61,7 +62,8 @@ public class Post extends AbstractExtension {
|
|||
|
||||
@JsonIgnore
|
||||
public boolean isPublished() {
|
||||
return Objects.equals(true, spec.getPublished());
|
||||
Map<String, String> labels = getMetadata().getLabels();
|
||||
return labels != null && labels.getOrDefault(PUBLISHED_LABEL, "false").equals("true");
|
||||
}
|
||||
|
||||
@Data
|
||||
|
@ -91,7 +93,7 @@ public class Post extends AbstractExtension {
|
|||
private Boolean deleted;
|
||||
|
||||
@Schema(required = true, defaultValue = "false")
|
||||
private Boolean published;
|
||||
private Boolean publish;
|
||||
|
||||
private Instant publishTime;
|
||||
|
||||
|
@ -138,6 +140,8 @@ public class Post extends AbstractExtension {
|
|||
|
||||
private List<String> contributors;
|
||||
|
||||
private List<String> releasedSnapshots;
|
||||
|
||||
@JsonIgnore
|
||||
public List<Condition> getConditionsOrDefault() {
|
||||
if (this.conditions == null) {
|
||||
|
@ -159,7 +163,8 @@ public class Post extends AbstractExtension {
|
|||
public enum PostPhase {
|
||||
DRAFT,
|
||||
PENDING_APPROVAL,
|
||||
PUBLISHED;
|
||||
PUBLISHED,
|
||||
FAILED;
|
||||
|
||||
/**
|
||||
* Convert string value to {@link PostPhase}.
|
||||
|
@ -252,4 +257,9 @@ public class Post extends AbstractExtension {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void changePublishedState(Post post, boolean value) {
|
||||
Map<String, String> labels = ExtensionUtil.nullSafeLabels(post);
|
||||
labels.put(PUBLISHED_LABEL, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.GVK;
|
||||
|
||||
/**
|
||||
|
@ -26,9 +26,9 @@ import run.halo.app.extension.GVK;
|
|||
public class SinglePage extends AbstractExtension {
|
||||
public static final String KIND = "SinglePage";
|
||||
public static final String DELETED_LABEL = "content.halo.run/deleted";
|
||||
public static final String PUBLISHED_LABEL = "content.halo.run/published";
|
||||
public static final String OWNER_LABEL = "content.halo.run/owner";
|
||||
public static final String VISIBLE_LABEL = "content.halo.run/visible";
|
||||
public static final String PHASE_LABEL = "content.halo.run/phase";
|
||||
|
||||
@Schema(required = true)
|
||||
private SinglePageSpec spec;
|
||||
|
@ -46,7 +46,8 @@ public class SinglePage extends AbstractExtension {
|
|||
|
||||
@JsonIgnore
|
||||
public boolean isPublished() {
|
||||
return Objects.equals(true, spec.getPublished());
|
||||
Map<String, String> labels = getMetadata().getLabels();
|
||||
return labels != null && labels.getOrDefault(PUBLISHED_LABEL, "false").equals("true");
|
||||
}
|
||||
|
||||
@Data
|
||||
|
@ -76,7 +77,7 @@ public class SinglePage extends AbstractExtension {
|
|||
private Boolean deleted;
|
||||
|
||||
@Schema(required = true, defaultValue = "false")
|
||||
private Boolean published;
|
||||
private Boolean publish;
|
||||
|
||||
private Instant publishTime;
|
||||
|
||||
|
@ -106,4 +107,9 @@ public class SinglePage extends AbstractExtension {
|
|||
public static class SinglePageStatus extends Post.PostStatus {
|
||||
|
||||
}
|
||||
|
||||
public static void changePublishedState(SinglePage page, boolean value) {
|
||||
Map<String, String> labels = ExtensionUtil.nullSafeLabels(page);
|
||||
labels.put(PUBLISHED_LABEL, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
@ -13,6 +14,7 @@ import run.halo.app.content.ContentWrapper;
|
|||
import run.halo.app.content.PatchUtils;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.GVK;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
|
@ -26,6 +28,7 @@ import run.halo.app.extension.GVK;
|
|||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Snapshot extends AbstractExtension {
|
||||
public static final String KIND = "Snapshot";
|
||||
public static final String PUBLISHED_LABEL = "content.halo.run/published";
|
||||
|
||||
@Schema(required = true)
|
||||
private SnapShotSpec spec;
|
||||
|
@ -34,7 +37,7 @@ public class Snapshot extends AbstractExtension {
|
|||
public static class SnapShotSpec {
|
||||
|
||||
@Schema(required = true)
|
||||
private SubjectRef subjectRef;
|
||||
private Ref subjectRef;
|
||||
|
||||
/**
|
||||
* such as: markdown | html | json | asciidoc | latex.
|
||||
|
@ -67,23 +70,6 @@ public class Snapshot extends AbstractExtension {
|
|||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
public static class SubjectRef {
|
||||
@Schema(required = true)
|
||||
private String kind;
|
||||
|
||||
@Schema(required = true)
|
||||
private String name;
|
||||
|
||||
public static SubjectRef of(String kind, String name) {
|
||||
SubjectRef subjectRef = new SubjectRef();
|
||||
subjectRef.setKind(kind);
|
||||
subjectRef.setName(name);
|
||||
return subjectRef;
|
||||
}
|
||||
}
|
||||
|
||||
public static String displayVersionFrom(Integer version) {
|
||||
Assert.notNull(version, "The version must not be null");
|
||||
return "v" + version;
|
||||
|
@ -91,7 +77,8 @@ public class Snapshot extends AbstractExtension {
|
|||
|
||||
@JsonIgnore
|
||||
public boolean isPublished() {
|
||||
return this.spec.getPublishTime() != null;
|
||||
Map<String, String> labels = getMetadata().getLabels();
|
||||
return labels != null && labels.getOrDefault(PUBLISHED_LABEL, "false").equals("true");
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
|
@ -101,29 +88,33 @@ public class Snapshot extends AbstractExtension {
|
|||
contributors.add(name);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setSubjectRef(String kind, String name) {
|
||||
Assert.notNull(kind, "The subject kind must not be null.");
|
||||
Assert.notNull(name, "The subject name must not be null.");
|
||||
if (spec.subjectRef == null) {
|
||||
spec.subjectRef = new SubjectRef();
|
||||
}
|
||||
spec.subjectRef.setKind(kind);
|
||||
spec.subjectRef.setName(name);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public ContentWrapper applyPatch(Snapshot baseSnapshot) {
|
||||
Assert.notNull(baseSnapshot, "The baseSnapshot must not be null.");
|
||||
if (this.spec.version == 1) {
|
||||
return new ContentWrapper(this.getMetadata().getName(), this.spec.rawPatch,
|
||||
this.spec.contentPatch, this.spec.rawType);
|
||||
return ContentWrapper.builder()
|
||||
.snapshotName(this.getMetadata().getName())
|
||||
.version(this.spec.version)
|
||||
.raw(this.spec.rawPatch)
|
||||
.content(this.spec.contentPatch)
|
||||
.rawType(this.spec.rawType)
|
||||
.build();
|
||||
}
|
||||
String patchedContent =
|
||||
PatchUtils.applyPatch(baseSnapshot.getSpec().getContentPatch(), this.spec.contentPatch);
|
||||
String patchedRaw =
|
||||
PatchUtils.applyPatch(baseSnapshot.getSpec().getRawPatch(), this.spec.rawPatch);
|
||||
return new ContentWrapper(this.getMetadata().getName(), patchedRaw,
|
||||
patchedContent, this.spec.rawType);
|
||||
return ContentWrapper.builder()
|
||||
.snapshotName(this.getMetadata().getName())
|
||||
.version(this.spec.version)
|
||||
.raw(patchedRaw)
|
||||
.content(patchedContent)
|
||||
.rawType(this.spec.rawType)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static void putPublishedLabel(Map<String, String> labels) {
|
||||
Assert.notNull(labels, "The labels must not be null.");
|
||||
labels.put(PUBLISHED_LABEL, "true");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
|||
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springdoc.core.fn.builders.schema.Builder;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -17,7 +19,6 @@ import reactor.core.publisher.Mono;
|
|||
import run.halo.app.content.ContentRequest;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
|
||||
/**
|
||||
* Endpoint for managing content.
|
||||
|
@ -47,7 +48,7 @@ public class ContentEndpoint implements CustomEndpoint {
|
|||
.name("snapshotName")
|
||||
.in(ParameterIn.PATH))
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentWrapper.class))
|
||||
.implementation(ContentResponse.class))
|
||||
)
|
||||
.POST("contents", this::draftSnapshotContent,
|
||||
builder -> builder.operationId("DraftSnapshotContent")
|
||||
|
@ -61,7 +62,7 @@ public class ContentEndpoint implements CustomEndpoint {
|
|||
.implementation(ContentRequest.class))
|
||||
))
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentWrapper.class))
|
||||
.implementation(ContentResponse.class))
|
||||
)
|
||||
.PUT("contents/{snapshotName}", this::updateSnapshotContent,
|
||||
builder -> builder.operationId("UpdateSnapshotContent")
|
||||
|
@ -79,25 +80,7 @@ public class ContentEndpoint implements CustomEndpoint {
|
|||
.implementation(ContentRequest.class))
|
||||
))
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentWrapper.class))
|
||||
)
|
||||
.PUT("contents/{snapshotName}/publish", this::publishSnapshotContent,
|
||||
builder -> builder.operationId("PublishSnapshotContent")
|
||||
.description("Publish a snapshot content.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.required(true)
|
||||
.name("snapshotName")
|
||||
.in(ParameterIn.PATH))
|
||||
.requestBody(requestBodyBuilder()
|
||||
.required(true)
|
||||
.content(contentBuilder()
|
||||
.mediaType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.schema(Builder.schemaBuilder()
|
||||
.implementation(Snapshot.SubjectRef.class))
|
||||
))
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentWrapper.class))
|
||||
.implementation(ContentResponse.class))
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
@ -105,6 +88,7 @@ public class ContentEndpoint implements CustomEndpoint {
|
|||
private Mono<ServerResponse> obtainContent(ServerRequest request) {
|
||||
String snapshotName = request.pathVariable("snapshotName");
|
||||
return contentService.getContent(snapshotName)
|
||||
.map(ContentResponse::from)
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
|
@ -117,19 +101,41 @@ public class ContentEndpoint implements CustomEndpoint {
|
|||
content.raw(), content.content(), content.rawType());
|
||||
return contentService.updateContent(contentRequest);
|
||||
})
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> publishSnapshotContent(ServerRequest request) {
|
||||
String snapshotName = request.pathVariable("snapshotName");
|
||||
return request.bodyToMono(Snapshot.SubjectRef.class)
|
||||
.flatMap(subjectRef -> contentService.publish(snapshotName, subjectRef))
|
||||
.map(ContentResponse::from)
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> draftSnapshotContent(ServerRequest request) {
|
||||
return request.bodyToMono(ContentRequest.class)
|
||||
.flatMap(contentService::draftContent)
|
||||
.map(ContentResponse::from)
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ContentResponse {
|
||||
@Schema(required = true, description = "The headSnapshotName if updated or new name if "
|
||||
+ "created.")
|
||||
private String snapshotName;
|
||||
@Schema(required = true)
|
||||
private String raw;
|
||||
|
||||
@Schema(required = true)
|
||||
private String content;
|
||||
|
||||
@Schema(required = true)
|
||||
private String rawType;
|
||||
|
||||
/**
|
||||
* Converts content response from {@link ContentWrapper}.
|
||||
*/
|
||||
public static ContentResponse from(ContentWrapper wrapper) {
|
||||
ContentResponse response = new ContentResponse();
|
||||
response.raw = wrapper.getRaw();
|
||||
response.setSnapshotName(wrapper.getSnapshotName());
|
||||
response.content = wrapper.getContent();
|
||||
response.rawType = wrapper.getRawType();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
|||
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springdoc.core.fn.builders.schema.Builder;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -20,6 +21,7 @@ import run.halo.app.content.PostRequest;
|
|||
import run.halo.app.content.PostService;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||
|
||||
/**
|
||||
|
@ -29,13 +31,11 @@ import run.halo.app.extension.router.QueryParamBuildUtil;
|
|||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class PostEndpoint implements CustomEndpoint {
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
public PostEndpoint(PostService postService) {
|
||||
this.postService = postService;
|
||||
}
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
|
@ -111,7 +111,14 @@ public class PostEndpoint implements CustomEndpoint {
|
|||
|
||||
Mono<ServerResponse> publishPost(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return postService.publishPost(name)
|
||||
return client.fetch(Post.class, name)
|
||||
.flatMap(post -> {
|
||||
Post.PostSpec spec = post.getSpec();
|
||||
spec.setPublish(true);
|
||||
spec.setReleaseSnapshot(spec.getHeadSnapshot());
|
||||
return client.update(post);
|
||||
})
|
||||
.flatMap(post -> postService.publishPost(post.getMetadata().getName()))
|
||||
.flatMap(post -> ServerResponse.ok().bodyValue(post));
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
|||
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springdoc.core.fn.builders.schema.Builder;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -20,6 +21,7 @@ import run.halo.app.content.SinglePageRequest;
|
|||
import run.halo.app.content.SinglePageService;
|
||||
import run.halo.app.core.extension.SinglePage;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||
|
||||
/**
|
||||
|
@ -29,13 +31,11 @@ import run.halo.app.extension.router.QueryParamBuildUtil;
|
|||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class SinglePageEndpoint implements CustomEndpoint {
|
||||
|
||||
private final SinglePageService singlePageService;
|
||||
|
||||
public SinglePageEndpoint(SinglePageService singlePageService) {
|
||||
this.singlePageService = singlePageService;
|
||||
}
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
|
@ -111,8 +111,15 @@ public class SinglePageEndpoint implements CustomEndpoint {
|
|||
|
||||
Mono<ServerResponse> publishSinglePage(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return singlePageService.publish(name)
|
||||
.flatMap(singlePage -> ServerResponse.ok().bodyValue(singlePage));
|
||||
return client.fetch(SinglePage.class, name)
|
||||
.flatMap(singlePage -> {
|
||||
SinglePage.SinglePageSpec spec = singlePage.getSpec();
|
||||
spec.setPublish(true);
|
||||
spec.setReleaseSnapshot(spec.getHeadSnapshot());
|
||||
return client.update(singlePage);
|
||||
})
|
||||
.flatMap(singlePage -> singlePageService.publish(singlePage.getMetadata().getName()))
|
||||
.flatMap(page -> ServerResponse.ok().bodyValue(page));
|
||||
}
|
||||
|
||||
Mono<ServerResponse> listSinglePage(ServerRequest request) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -11,12 +11,14 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.jsoup.Jsoup;
|
||||
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.Comment;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.ExtensionOperator;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.infra.Condition;
|
||||
|
@ -42,13 +44,16 @@ 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;
|
||||
|
||||
public PostReconciler(ExtensionClient client, ContentService contentService,
|
||||
PostPermalinkPolicy postPermalinkPolicy, CounterService counterService) {
|
||||
PostService postService, PostPermalinkPolicy postPermalinkPolicy,
|
||||
CounterService counterService) {
|
||||
this.client = client;
|
||||
this.contentService = contentService;
|
||||
this.postService = postService;
|
||||
this.postPermalinkPolicy = postPermalinkPolicy;
|
||||
this.counterService = counterService;
|
||||
}
|
||||
|
@ -63,26 +68,90 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
}
|
||||
addFinalizerIfNecessary(post);
|
||||
|
||||
// reconcile spec first
|
||||
reconcileSpec(request.name());
|
||||
reconcileMetadata(request.name());
|
||||
reconcileStatus(request.name());
|
||||
});
|
||||
return new Result(false, null);
|
||||
}
|
||||
|
||||
private void reconcileSpec(String name) {
|
||||
// publish post if necessary
|
||||
try {
|
||||
postService.publishPost(name).block();
|
||||
} catch (Throwable e) {
|
||||
publishFailed(name, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
client.fetch(Post.class, name).ifPresent(post -> {
|
||||
Post oldPost = JsonUtils.deepCopy(post);
|
||||
if (post.isPublished() && Objects.equals(false, post.getSpec().getPublish())) {
|
||||
Post.changePublishedState(post, false);
|
||||
final Post.PostStatus status = post.getStatusOrDefault();
|
||||
Condition condition = new Condition();
|
||||
condition.setType("CancelledPublish");
|
||||
condition.setStatus(ConditionStatus.TRUE);
|
||||
condition.setReason(condition.getType());
|
||||
condition.setMessage("CancelledPublish");
|
||||
condition.setLastTransitionTime(Instant.now());
|
||||
status.getConditionsOrDefault().add(condition);
|
||||
status.setPhase(Post.PostPhase.DRAFT.name());
|
||||
}
|
||||
if (!oldPost.equals(post)) {
|
||||
client.update(post);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void publishFailed(String name, Throwable error) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(error, "Error must not be null");
|
||||
client.fetch(Post.class, name).ifPresent(post -> {
|
||||
final Post oldPost = JsonUtils.deepCopy(post);
|
||||
|
||||
Post.PostStatus status = post.getStatusOrDefault();
|
||||
Post.PostPhase phase = Post.PostPhase.FAILED;
|
||||
status.setPhase(phase.name());
|
||||
|
||||
final List<Condition> conditions = status.getConditionsOrDefault();
|
||||
Condition condition = new Condition();
|
||||
condition.setType(phase.name());
|
||||
condition.setReason(phase.name());
|
||||
condition.setMessage("");
|
||||
condition.setStatus(ConditionStatus.TRUE);
|
||||
condition.setLastTransitionTime(Instant.now());
|
||||
condition.setMessage(error.getMessage());
|
||||
condition.setStatus(ConditionStatus.FALSE);
|
||||
|
||||
if (conditions.size() > 0) {
|
||||
Condition lastCondition = conditions.get(conditions.size() - 1);
|
||||
if (!StringUtils.equals(lastCondition.getType(), condition.getType())
|
||||
&& !StringUtils.equals(lastCondition.getMessage(), condition.getMessage())) {
|
||||
conditions.add(condition);
|
||||
}
|
||||
}
|
||||
post.setStatus(status);
|
||||
|
||||
if (!oldPost.equals(post)) {
|
||||
client.update(post);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
Map<String, String> labels = ExtensionUtil.nullSafeLabels(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());
|
||||
|
@ -91,7 +160,9 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
labels.put(Post.ARCHIVE_YEAR_LABEL, HaloUtils.getYearText(publishTime));
|
||||
labels.put(Post.ARCHIVE_MONTH_LABEL, HaloUtils.getMonthText(publishTime));
|
||||
}
|
||||
|
||||
if (!labels.containsKey(Post.PUBLISHED_LABEL)) {
|
||||
labels.put(Post.PUBLISHED_LABEL, Boolean.FALSE.toString());
|
||||
}
|
||||
if (!oldPost.equals(post)) {
|
||||
client.update(post);
|
||||
}
|
||||
|
@ -123,16 +194,17 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
contentService.getContent(spec.getReleaseSnapshot())
|
||||
.blockOptional()
|
||||
.ifPresent(content -> {
|
||||
String contentRevised = content.content();
|
||||
String contentRevised = content.getContent();
|
||||
status.setExcerpt(getExcerpt(contentRevised));
|
||||
});
|
||||
} else {
|
||||
status.setExcerpt(excerpt.getRaw());
|
||||
}
|
||||
|
||||
Ref ref = Ref.of(post);
|
||||
// handle contributors
|
||||
String headSnapshot = post.getSpec().getHeadSnapshot();
|
||||
contentService.listSnapshots(Snapshot.SubjectRef.of(Post.KIND, name))
|
||||
contentService.listSnapshots(ref)
|
||||
.collectList()
|
||||
.blockOptional()
|
||||
.ifPresent(snapshots -> {
|
||||
|
@ -154,24 +226,18 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
snapshot -> snapshot.getMetadata().getName().equals(headSnapshot))
|
||||
.findAny()
|
||||
.ifPresent(snapshot -> {
|
||||
status.setInProgress(!isPublished(snapshot));
|
||||
status.setInProgress(!snapshot.isPublished());
|
||||
});
|
||||
|
||||
List<String> releasedSnapshots = snapshots.stream()
|
||||
.filter(Snapshot::isPublished)
|
||||
.sorted(Comparator.comparing(snapshot -> snapshot.getSpec().getVersion()))
|
||||
.map(snapshot -> snapshot.getMetadata().getName())
|
||||
.toList();
|
||||
status.setReleasedSnapshots(releasedSnapshots);
|
||||
});
|
||||
|
||||
// handle cancel publish,has released version and published is false and not handled
|
||||
if (StringUtils.isNotBlank(spec.getReleaseSnapshot())
|
||||
&& Objects.equals(false, spec.getPublished())
|
||||
&& !StringUtils.equals(status.getPhase(), Post.PostPhase.DRAFT.name())) {
|
||||
Condition condition = new Condition();
|
||||
condition.setType("CancelledPublish");
|
||||
condition.setStatus(ConditionStatus.TRUE);
|
||||
condition.setReason(condition.getType());
|
||||
condition.setMessage(StringUtils.EMPTY);
|
||||
condition.setLastTransitionTime(Instant.now());
|
||||
status.getConditionsOrDefault().add(condition);
|
||||
status.setPhase(Post.PostPhase.DRAFT.name());
|
||||
}
|
||||
status.setConditions(limitConditionSize(status.getConditions()));
|
||||
|
||||
if (!oldPost.equals(post)) {
|
||||
client.update(post);
|
||||
|
@ -211,14 +277,12 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
postPermalinkPolicy.onPermalinkDelete(post);
|
||||
|
||||
// clean up snapshots
|
||||
Snapshot.SubjectRef subjectRef =
|
||||
Snapshot.SubjectRef.of(Post.KIND, post.getMetadata().getName());
|
||||
final Ref ref = Ref.of(post);
|
||||
client.list(Snapshot.class,
|
||||
snapshot -> subjectRef.equals(snapshot.getSpec().getSubjectRef()), null)
|
||||
snapshot -> ref.equals(snapshot.getSpec().getSubjectRef()), null)
|
||||
.forEach(client::delete);
|
||||
|
||||
// clean up comments
|
||||
Ref ref = Ref.of(post);
|
||||
client.list(Comment.class, comment -> comment.getSpec().getSubjectRef().equals(ref),
|
||||
null)
|
||||
.forEach(client::delete);
|
||||
|
@ -228,16 +292,6 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
.block();
|
||||
}
|
||||
|
||||
private Map<String, String> getLabelsOrDefault(Post post) {
|
||||
Assert.notNull(post, "The post must not be null.");
|
||||
Map<String, String> labels = post.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
labels = new LinkedHashMap<>();
|
||||
post.getMetadata().setLabels(labels);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
private String getExcerpt(String htmlContent) {
|
||||
String shortHtmlContent = StringUtils.substring(htmlContent, 0, 500);
|
||||
String text = Jsoup.parse(shortHtmlContent).text();
|
||||
|
@ -245,11 +299,11 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
return StringUtils.substring(text, 0, 150);
|
||||
}
|
||||
|
||||
private boolean isPublished(Snapshot snapshot) {
|
||||
return snapshot.getSpec().getPublishTime() != null;
|
||||
}
|
||||
|
||||
private boolean isPublished(Post post) {
|
||||
return Objects.equals(true, post.getSpec().getPublished());
|
||||
static List<Condition> limitConditionSize(List<Condition> conditions) {
|
||||
if (conditions == null || conditions.size() <= 10) {
|
||||
return conditions;
|
||||
}
|
||||
// Retain the last ten conditions
|
||||
return conditions.subList(conditions.size() - 10, conditions.size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,17 +4,19 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||
import static org.springframework.web.util.UriUtils.encodePath;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.SinglePageService;
|
||||
import run.halo.app.content.permalinks.ExtensionLocator;
|
||||
import run.halo.app.core.extension.Comment;
|
||||
import run.halo.app.core.extension.Post;
|
||||
|
@ -22,6 +24,7 @@ import run.halo.app.core.extension.SinglePage;
|
|||
import run.halo.app.core.extension.Snapshot;
|
||||
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.Reconciler;
|
||||
|
@ -46,22 +49,26 @@ import run.halo.app.theme.router.PermalinkIndexDeleteCommand;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
||||
private static final String FINALIZER_NAME = "single-page-protection";
|
||||
private static final GroupVersionKind GVK = GroupVersionKind.fromExtension(SinglePage.class);
|
||||
private final ExtensionClient client;
|
||||
private final ContentService contentService;
|
||||
private final ApplicationContext applicationContext;
|
||||
private final SinglePageService singlePageService;
|
||||
private final CounterService counterService;
|
||||
|
||||
private final ExternalUrlSupplier externalUrlSupplier;
|
||||
|
||||
public SinglePageReconciler(ExtensionClient client, ContentService contentService,
|
||||
ApplicationContext applicationContext, CounterService counterService,
|
||||
ApplicationContext applicationContext, SinglePageService singlePageService,
|
||||
CounterService counterService,
|
||||
ExternalUrlSupplier externalUrlSupplier) {
|
||||
this.client = client;
|
||||
this.contentService = contentService;
|
||||
this.applicationContext = applicationContext;
|
||||
this.singlePageService = singlePageService;
|
||||
this.counterService = counterService;
|
||||
this.externalUrlSupplier = externalUrlSupplier;
|
||||
}
|
||||
|
@ -77,12 +84,79 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
}
|
||||
addFinalizerIfNecessary(oldPage);
|
||||
|
||||
reconcileStatus(request.name());
|
||||
// reconcile spec first
|
||||
reconcileSpec(request.name());
|
||||
// then
|
||||
reconcileMetadata(request.name());
|
||||
reconcileStatus(request.name());
|
||||
});
|
||||
return new Result(false, null);
|
||||
}
|
||||
|
||||
private void reconcileSpec(String name) {
|
||||
// publish single page if necessary
|
||||
try {
|
||||
singlePageService.publish(name).block();
|
||||
} catch (Throwable e) {
|
||||
publishFailed(name, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
client.fetch(SinglePage.class, name).ifPresent(page -> {
|
||||
SinglePage oldPage = JsonUtils.deepCopy(page);
|
||||
if (page.isPublished() && Objects.equals(false, page.getSpec().getPublish())) {
|
||||
SinglePage.changePublishedState(page, false);
|
||||
final SinglePage.SinglePageStatus status = page.getStatusOrDefault();
|
||||
Condition condition = new Condition();
|
||||
condition.setType("CancelledPublish");
|
||||
condition.setStatus(ConditionStatus.TRUE);
|
||||
condition.setReason(condition.getType());
|
||||
condition.setMessage("CancelledPublish");
|
||||
condition.setLastTransitionTime(Instant.now());
|
||||
status.getConditionsOrDefault().add(condition);
|
||||
status.setPhase(Post.PostPhase.DRAFT.name());
|
||||
}
|
||||
if (!oldPage.equals(page)) {
|
||||
client.update(page);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void publishFailed(String name, Throwable error) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(error, "Error must not be null");
|
||||
client.fetch(SinglePage.class, name).ifPresent(page -> {
|
||||
final SinglePage oldPage = JsonUtils.deepCopy(page);
|
||||
|
||||
SinglePage.SinglePageStatus status = page.getStatusOrDefault();
|
||||
Post.PostPhase phase = Post.PostPhase.FAILED;
|
||||
status.setPhase(phase.name());
|
||||
|
||||
final List<Condition> conditions = status.getConditionsOrDefault();
|
||||
Condition condition = new Condition();
|
||||
condition.setType(phase.name());
|
||||
condition.setReason(phase.name());
|
||||
condition.setMessage("");
|
||||
condition.setStatus(ConditionStatus.TRUE);
|
||||
condition.setLastTransitionTime(Instant.now());
|
||||
condition.setMessage(error.getMessage());
|
||||
condition.setStatus(ConditionStatus.FALSE);
|
||||
|
||||
if (conditions.size() > 0) {
|
||||
Condition lastCondition = conditions.get(conditions.size() - 1);
|
||||
if (!StringUtils.equals(lastCondition.getType(), condition.getType())
|
||||
&& !StringUtils.equals(lastCondition.getMessage(), condition.getMessage())) {
|
||||
conditions.add(condition);
|
||||
}
|
||||
}
|
||||
page.setStatus(status);
|
||||
|
||||
if (!oldPage.equals(page)) {
|
||||
client.update(page);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addFinalizerIfNecessary(SinglePage oldSinglePage) {
|
||||
Set<String> finalizers = oldSinglePage.getMetadata().getFinalizers();
|
||||
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||
|
@ -105,14 +179,12 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
permalinkOnDelete(singlePage);
|
||||
|
||||
// clean up snapshot
|
||||
Snapshot.SubjectRef subjectRef =
|
||||
Snapshot.SubjectRef.of(SinglePage.KIND, singlePage.getMetadata().getName());
|
||||
Ref ref = Ref.of(singlePage);
|
||||
client.list(Snapshot.class,
|
||||
snapshot -> subjectRef.equals(snapshot.getSpec().getSubjectRef()), null)
|
||||
snapshot -> ref.equals(snapshot.getSpec().getSubjectRef()), null)
|
||||
.forEach(client::delete);
|
||||
|
||||
// clean up comments
|
||||
Ref ref = Ref.of(singlePage);
|
||||
client.list(Comment.class, comment -> comment.getSpec().getSubjectRef().equals(ref),
|
||||
null)
|
||||
.forEach(client::delete);
|
||||
|
@ -139,17 +211,18 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
|
||||
SinglePage.SinglePageSpec spec = singlePage.getSpec();
|
||||
// handle logic delete
|
||||
Map<String, String> labels = getLabelsOrDefault(singlePage);
|
||||
Map<String, String> labels = ExtensionUtil.nullSafeLabels(singlePage);
|
||||
if (isDeleted(singlePage)) {
|
||||
labels.put(SinglePage.DELETED_LABEL, Boolean.TRUE.toString());
|
||||
} else {
|
||||
labels.put(SinglePage.DELETED_LABEL, Boolean.FALSE.toString());
|
||||
}
|
||||
// synchronize some fields to labels to query
|
||||
labels.put(SinglePage.PHASE_LABEL, singlePage.getStatusOrDefault().getPhase());
|
||||
labels.put(SinglePage.VISIBLE_LABEL,
|
||||
Objects.requireNonNullElse(spec.getVisible(), Post.VisibleEnum.PUBLIC).name());
|
||||
labels.put(SinglePage.OWNER_LABEL, spec.getOwner());
|
||||
if (!labels.containsKey(SinglePage.PUBLISHED_LABEL)) {
|
||||
labels.put(Post.PUBLISHED_LABEL, Boolean.FALSE.toString());
|
||||
}
|
||||
if (!oldPage.equals(singlePage)) {
|
||||
client.update(singlePage);
|
||||
}
|
||||
|
@ -205,7 +278,7 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
contentService.getContent(spec.getHeadSnapshot())
|
||||
.blockOptional()
|
||||
.ifPresent(content -> {
|
||||
String contentRevised = content.content();
|
||||
String contentRevised = content.getContent();
|
||||
status.setExcerpt(getExcerpt(contentRevised));
|
||||
});
|
||||
} else {
|
||||
|
@ -214,7 +287,7 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
|
||||
// handle contributors
|
||||
String headSnapshot = singlePage.getSpec().getHeadSnapshot();
|
||||
contentService.listSnapshots(Snapshot.SubjectRef.of(SinglePage.KIND, name))
|
||||
contentService.listSnapshots(Ref.of(singlePage))
|
||||
.collectList()
|
||||
.blockOptional().ifPresent(snapshots -> {
|
||||
List<String> contributors = snapshots.stream()
|
||||
|
@ -234,23 +307,18 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
.filter(snapshot -> snapshot.getMetadata().getName().equals(headSnapshot))
|
||||
.findAny()
|
||||
.ifPresent(snapshot -> {
|
||||
status.setInProgress(!isPublished(snapshot));
|
||||
status.setInProgress(!snapshot.isPublished());
|
||||
});
|
||||
|
||||
List<String> releasedSnapshots = snapshots.stream()
|
||||
.filter(Snapshot::isPublished)
|
||||
.sorted(Comparator.comparing(snapshot -> snapshot.getSpec().getVersion()))
|
||||
.map(snapshot -> snapshot.getMetadata().getName())
|
||||
.toList();
|
||||
status.setReleasedSnapshots(releasedSnapshots);
|
||||
});
|
||||
|
||||
// handle cancel publish,has released version and published is false and not handled
|
||||
if (StringUtils.isNotBlank(spec.getReleaseSnapshot())
|
||||
&& Objects.equals(false, spec.getPublished())
|
||||
&& !StringUtils.equals(status.getPhase(), Post.PostPhase.DRAFT.name())) {
|
||||
Condition condition = new Condition();
|
||||
condition.setType("CancelledPublish");
|
||||
condition.setStatus(ConditionStatus.TRUE);
|
||||
condition.setReason(condition.getType());
|
||||
condition.setMessage(StringUtils.EMPTY);
|
||||
condition.setLastTransitionTime(Instant.now());
|
||||
status.getConditionsOrDefault().add(condition);
|
||||
status.setPhase(Post.PostPhase.DRAFT.name());
|
||||
}
|
||||
status.setConditions(limitConditionSize(status.getConditions()));
|
||||
|
||||
if (!oldPage.equals(singlePage)) {
|
||||
client.update(singlePage);
|
||||
|
@ -258,16 +326,6 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
});
|
||||
}
|
||||
|
||||
private Map<String, String> getLabelsOrDefault(SinglePage singlePage) {
|
||||
Assert.notNull(singlePage, "The singlePage must not be null.");
|
||||
Map<String, String> labels = singlePage.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
labels = new LinkedHashMap<>();
|
||||
singlePage.getMetadata().setLabels(labels);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
private String getExcerpt(String htmlContent) {
|
||||
String shortHtmlContent = StringUtils.substring(htmlContent, 0, 500);
|
||||
String text = Jsoup.parse(shortHtmlContent).text();
|
||||
|
@ -275,16 +333,16 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
return StringUtils.substring(text, 0, 150);
|
||||
}
|
||||
|
||||
private boolean isPublished(Snapshot snapshot) {
|
||||
return snapshot.getSpec().getPublishTime() != null;
|
||||
}
|
||||
|
||||
private boolean isPublished(SinglePage singlePage) {
|
||||
return Objects.equals(true, singlePage.getSpec().getPublished());
|
||||
}
|
||||
|
||||
private boolean isDeleted(SinglePage singlePage) {
|
||||
return Objects.equals(true, singlePage.getSpec().getDeleted())
|
||||
|| singlePage.getMetadata().getDeletionTimestamp() != null;
|
||||
}
|
||||
|
||||
static List<Condition> limitConditionSize(List<Condition> conditions) {
|
||||
if (conditions == null || conditions.size() <= 10) {
|
||||
return conditions;
|
||||
}
|
||||
// Retain the last ten conditions
|
||||
return conditions.subList(conditions.size() - 10, conditions.size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package run.halo.app.extension;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -38,4 +41,21 @@ public final class ExtensionUtil {
|
|||
public static String buildStoreName(Scheme scheme, String name) {
|
||||
return buildStoreNamePrefix(scheme) + "/" + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extension metadata labels null safe.
|
||||
*
|
||||
* @param extension extension must not be null
|
||||
* @return extension metadata labels
|
||||
*/
|
||||
public static Map<String, String> nullSafeLabels(AbstractExtension extension) {
|
||||
Assert.notNull(extension, "The extension must not be null.");
|
||||
Assert.notNull(extension.getMetadata(), "The extension metadata must not be null.");
|
||||
Map<String, String> labels = extension.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
labels = new HashMap<>();
|
||||
extension.getMetadata().setLabels(labels);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.infra.utils;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -11,6 +12,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class PathUtils {
|
||||
|
||||
|
@ -42,7 +44,9 @@ public class PathUtils {
|
|||
URI uri = new URI(uriString);
|
||||
return uri.isAbsolute();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
log.debug("Failed to parse uri: " + uriString, e);
|
||||
// ignore this exception
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,9 +49,8 @@ import run.halo.app.theme.finders.vo.TagVo;
|
|||
@Finder("postFinder")
|
||||
public class PostFinderImpl implements PostFinder {
|
||||
|
||||
public static final Predicate<Post> FIXED_PREDICATE = post ->
|
||||
Objects.equals(false, post.getSpec().getDeleted())
|
||||
&& Objects.equals(true, post.getSpec().getPublished())
|
||||
public static final Predicate<Post> FIXED_PREDICATE = post -> post.isPublished()
|
||||
&& Objects.equals(false, post.getSpec().getDeleted())
|
||||
&& Post.VisibleEnum.PUBLIC.equals(post.getSpec().getVisible());
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
|
@ -95,8 +94,8 @@ public class PostFinderImpl implements PostFinder {
|
|||
return client.fetch(Post.class, postName)
|
||||
.map(post -> post.getSpec().getReleaseSnapshot())
|
||||
.flatMap(contentService::getContent)
|
||||
.map(wrapper -> ContentVo.builder().content(wrapper.content())
|
||||
.raw(wrapper.raw()).build())
|
||||
.map(wrapper -> ContentVo.builder().content(wrapper.getContent())
|
||||
.raw(wrapper.getRaw()).build())
|
||||
.block();
|
||||
}
|
||||
|
||||
|
@ -316,6 +315,7 @@ public class PostFinderImpl implements PostFinder {
|
|||
postVo.setCategories(categoryVos);
|
||||
postVo.setTags(tags);
|
||||
postVo.setContributors(contributors);
|
||||
postVo.setOwner(contributorFinder.getContributor(post.getSpec().getOwner()));
|
||||
populateStats(postVo);
|
||||
return postVo;
|
||||
}
|
||||
|
|
|
@ -32,10 +32,9 @@ import run.halo.app.theme.finders.vo.StatsVo;
|
|||
@Finder("singlePageFinder")
|
||||
public class SinglePageFinderImpl implements SinglePageFinder {
|
||||
|
||||
public static final Predicate<SinglePage> FIXED_PREDICATE = page ->
|
||||
Objects.equals(false, page.getSpec().getDeleted())
|
||||
&& Objects.equals(true, page.getSpec().getPublished())
|
||||
&& Post.VisibleEnum.PUBLIC.equals(page.getSpec().getVisible());
|
||||
public static final Predicate<SinglePage> FIXED_PREDICATE = page -> page.isPublished()
|
||||
&& Objects.equals(false, page.getSpec().getDeleted())
|
||||
&& Post.VisibleEnum.PUBLIC.equals(page.getSpec().getVisible());
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
|
@ -65,6 +64,7 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
|||
SinglePageVo pageVo = SinglePageVo.from(page);
|
||||
pageVo.setContributors(contributors);
|
||||
pageVo.setContent(content(pageName));
|
||||
pageVo.setOwner(contributorFinder.getContributor(page.getSpec().getOwner()));
|
||||
populateStats(pageVo);
|
||||
return pageVo;
|
||||
}
|
||||
|
@ -74,8 +74,8 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
|||
return client.fetch(SinglePage.class, pageName)
|
||||
.map(page -> page.getSpec().getReleaseSnapshot())
|
||||
.flatMap(contentService::getContent)
|
||||
.map(wrapper -> ContentVo.builder().content(wrapper.content())
|
||||
.raw(wrapper.raw()).build())
|
||||
.map(wrapper -> ContentVo.builder().content(wrapper.getContent())
|
||||
.raw(wrapper.getRaw()).build())
|
||||
.block();
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ public class PostVo {
|
|||
|
||||
private List<Contributor> contributors;
|
||||
|
||||
private Contributor owner;
|
||||
|
||||
private StatsVo stats;
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,6 +33,8 @@ public class SinglePageVo {
|
|||
|
||||
private List<Contributor> contributors;
|
||||
|
||||
private Contributor owner;
|
||||
|
||||
/**
|
||||
* Convert {@link SinglePage} to {@link SinglePageVo}.
|
||||
*
|
||||
|
|
|
@ -19,6 +19,10 @@ data:
|
|||
"globalHead": "",
|
||||
"footer": ""
|
||||
}
|
||||
post: |
|
||||
{
|
||||
"review": false
|
||||
}
|
||||
menu: |
|
||||
{
|
||||
"primary": "primary"
|
||||
|
|
|
@ -50,12 +50,7 @@ spec:
|
|||
value: 10
|
||||
min: 1
|
||||
max: 100
|
||||
validation: required | max:100
|
||||
- $formkit: checkbox
|
||||
label: "新文章审核"
|
||||
value: false
|
||||
name: review
|
||||
help: "用户发布文章是否需要管理员审核"
|
||||
validation: required
|
||||
- group: seo
|
||||
label: SEO 设置
|
||||
formSchema:
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test;
|
|||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
|
@ -19,8 +20,11 @@ class ContentRequestTest {
|
|||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, "test-post");
|
||||
contentRequest = new ContentRequest(subjectRef, "snapshot-1", """
|
||||
Ref ref = new Ref();
|
||||
ref.setKind(Post.KIND);
|
||||
ref.setGroup("content.halo.run");
|
||||
ref.setName("test-post");
|
||||
contentRequest = new ContentRequest(ref, "snapshot-1", """
|
||||
Four score and seven
|
||||
years ago our fathers
|
||||
|
||||
|
@ -50,6 +54,7 @@ class ContentRequestTest {
|
|||
"spec": {
|
||||
"subjectRef": {
|
||||
"kind": "Post",
|
||||
"group": "content.halo.run",
|
||||
"name": "test-post"
|
||||
},
|
||||
"rawType": "MARKDOWN",
|
||||
|
|
|
@ -11,6 +11,7 @@ import static run.halo.app.content.TestPost.snapshotV2;
|
|||
import static run.halo.app.content.TestPost.snapshotV3;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
@ -25,6 +26,7 @@ import run.halo.app.content.impl.ContentServiceImpl;
|
|||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
|
@ -50,20 +52,23 @@ class ContentServiceTest {
|
|||
@Test
|
||||
void draftContent() {
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, "test-post");
|
||||
snapshotV1.getSpec().setSubjectRef(subjectRef);
|
||||
Ref ref = postRef("test-post");
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
|
||||
ContentRequest contentRequest =
|
||||
new ContentRequest(subjectRef, null,
|
||||
new ContentRequest(ref, null,
|
||||
snapshotV1.getSpec().getRawPatch(),
|
||||
snapshotV1.getSpec().getContentPatch(),
|
||||
snapshotV1.getSpec().getRawType());
|
||||
|
||||
pilingBaseSnapshot(snapshotV1);
|
||||
|
||||
ContentWrapper contentWrapper =
|
||||
new ContentWrapper("snapshot-A", contentRequest.raw(),
|
||||
contentRequest.content(), snapshotV1.getSpec().getRawType());
|
||||
ContentWrapper contentWrapper = ContentWrapper.builder()
|
||||
.snapshotName("snapshot-A")
|
||||
.version(1)
|
||||
.raw(contentRequest.raw())
|
||||
.content(contentRequest.content())
|
||||
.rawType(snapshotV1.getSpec().getRawType())
|
||||
.build();
|
||||
|
||||
ArgumentCaptor<Snapshot> captor = ArgumentCaptor.forClass(Snapshot.class);
|
||||
when(client.create(any())).thenReturn(Mono.just(snapshotV1));
|
||||
|
@ -77,7 +82,7 @@ class ContentServiceTest {
|
|||
Snapshot snapshot = captor.getValue();
|
||||
|
||||
snapshotV1.getMetadata().setName(snapshot.getMetadata().getName());
|
||||
snapshotV1.getSpec().setSubjectRef(subjectRef);
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
assertThat(snapshot).isEqualTo(snapshotV1);
|
||||
}
|
||||
|
||||
|
@ -85,14 +90,14 @@ class ContentServiceTest {
|
|||
void updateContent() {
|
||||
String headSnapshot = "snapshot-A";
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, "test-post");
|
||||
Ref ref = postRef("test-post");
|
||||
|
||||
Snapshot updated = snapshotV1();
|
||||
updated.getSpec().setRawPatch("hello");
|
||||
updated.getSpec().setContentPatch("<p>hello</p>");
|
||||
updated.getSpec().setSubjectRef(subjectRef);
|
||||
updated.getSpec().setSubjectRef(ref);
|
||||
ContentRequest contentRequest =
|
||||
new ContentRequest(subjectRef, headSnapshot,
|
||||
new ContentRequest(ref, headSnapshot,
|
||||
snapshotV1.getSpec().getRawPatch(),
|
||||
snapshotV1.getSpec().getContentPatch(),
|
||||
snapshotV1.getSpec().getRawType());
|
||||
|
@ -102,9 +107,13 @@ class ContentServiceTest {
|
|||
when(client.fetch(eq(Snapshot.class), eq(contentRequest.headSnapshotName())))
|
||||
.thenReturn(Mono.just(updated));
|
||||
|
||||
ContentWrapper contentWrapper =
|
||||
new ContentWrapper(headSnapshot, contentRequest.raw(),
|
||||
contentRequest.content(), snapshotV1.getSpec().getRawType());
|
||||
ContentWrapper contentWrapper = ContentWrapper.builder()
|
||||
.snapshotName(headSnapshot)
|
||||
.version(1)
|
||||
.raw(contentRequest.raw())
|
||||
.content(contentRequest.content())
|
||||
.rawType(snapshotV1.getSpec().getRawType())
|
||||
.build();
|
||||
|
||||
ArgumentCaptor<Snapshot> captor = ArgumentCaptor.forClass(Snapshot.class);
|
||||
when(client.update(any())).thenReturn(Mono.just(updated));
|
||||
|
@ -124,12 +133,15 @@ class ContentServiceTest {
|
|||
void updateContentWhenHasDraftVersionButHeadPoints2Published() {
|
||||
final String headSnapshot = "snapshot-A";
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
final Ref ref = postRef("test-post");
|
||||
|
||||
Snapshot snapshotV2 = snapshotV2();
|
||||
snapshotV2.getSpec().setPublishTime(null);
|
||||
|
||||
|
||||
// v1(released),v2
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV1.getMetadata().getLabels());
|
||||
snapshotV1.getSpec().setPublishTime(Instant.now());
|
||||
pilingBaseSnapshot(snapshotV2, snapshotV1);
|
||||
|
||||
|
@ -138,23 +150,25 @@ class ContentServiceTest {
|
|||
when(client.fetch(eq(Snapshot.class), eq(snapshotV1.getMetadata().getName())))
|
||||
.thenReturn(Mono.just(snapshotV1));
|
||||
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, "test-post");
|
||||
|
||||
ContentRequest contentRequest =
|
||||
new ContentRequest(subjectRef, headSnapshot, "C",
|
||||
final ContentRequest contentRequest =
|
||||
new ContentRequest(ref, headSnapshot, "C",
|
||||
"<p>C</p>", snapshotV1.getSpec().getRawType());
|
||||
|
||||
when(client.create(any())).thenReturn(Mono.just(snapshotV3()));
|
||||
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(subjectRef))
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(ref))
|
||||
.expectNext(snapshotV2)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
Snapshot publishedV2 = snapshotV2();
|
||||
publishedV2.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(publishedV2.getMetadata().getLabels());
|
||||
when(client.update(any())).thenReturn(Mono.just(publishedV2));
|
||||
StepVerifier.create(contentService.updateContent(contentRequest))
|
||||
.consumeNextWith(created -> {
|
||||
assertThat(created.raw()).isEqualTo("C");
|
||||
assertThat(created.content()).isEqualTo("<p>C</p>");
|
||||
assertThat(created.getRaw()).isEqualTo("C");
|
||||
assertThat(created.getContent()).isEqualTo("<p>C</p>");
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
@ -162,15 +176,16 @@ class ContentServiceTest {
|
|||
|
||||
@Test
|
||||
void updateContentWhenHeadPoints2Published() {
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, "test-post");
|
||||
|
||||
final Ref ref = postRef("test-post");
|
||||
// v1(released),v2
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV1.getMetadata().getLabels());
|
||||
snapshotV1.getSpec().setPublishTime(Instant.now());
|
||||
snapshotV1.getSpec().setSubjectRef(subjectRef);
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
|
||||
Snapshot snapshotV2 = snapshotV2();
|
||||
snapshotV2.getSpec().setSubjectRef(subjectRef);
|
||||
snapshotV2.getSpec().setSubjectRef(ref);
|
||||
snapshotV2.getSpec().setPublishTime(null);
|
||||
|
||||
final String headSnapshot = snapshotV2.getMetadata().getName();
|
||||
|
@ -184,20 +199,20 @@ class ContentServiceTest {
|
|||
.thenReturn(Mono.just(snapshotV1));
|
||||
|
||||
ContentRequest contentRequest =
|
||||
new ContentRequest(subjectRef, headSnapshot, "C",
|
||||
new ContentRequest(ref, headSnapshot, "C",
|
||||
"<p>C</p>", snapshotV1.getSpec().getRawType());
|
||||
|
||||
when(client.update(any())).thenReturn(Mono.just(snapshotV2()));
|
||||
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(subjectRef))
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(ref))
|
||||
.expectNext(snapshotV2)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
StepVerifier.create(contentService.updateContent(contentRequest))
|
||||
.consumeNextWith(updated -> {
|
||||
assertThat(updated.raw()).isEqualTo("C");
|
||||
assertThat(updated.content()).isEqualTo("<p>C</p>");
|
||||
assertThat(updated.getRaw()).isEqualTo("C");
|
||||
assertThat(updated.getContent()).isEqualTo("<p>C</p>");
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
@ -207,12 +222,11 @@ class ContentServiceTest {
|
|||
|
||||
@Test
|
||||
void publishContent() {
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, "test-post");
|
||||
|
||||
Ref ref = postRef("test-post");
|
||||
// v1(released),v2
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.getSpec().setPublishTime(null);
|
||||
snapshotV1.getSpec().setSubjectRef(subjectRef);
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
|
||||
final String headSnapshot = snapshotV1.getMetadata().getName();
|
||||
|
||||
|
@ -223,7 +237,7 @@ class ContentServiceTest {
|
|||
|
||||
when(client.update(any())).thenReturn(Mono.just(snapshotV2()));
|
||||
|
||||
StepVerifier.create(contentService.publish(headSnapshot, subjectRef))
|
||||
StepVerifier.create(contentService.publish(headSnapshot, ref))
|
||||
.expectNext()
|
||||
.consumeNextWith(p -> {
|
||||
System.out.println(JsonUtils.objectToJson(p));
|
||||
|
@ -234,14 +248,25 @@ class ContentServiceTest {
|
|||
verify(client, times(1)).update(any());
|
||||
}
|
||||
|
||||
private static Ref postRef(String name) {
|
||||
Ref ref = new Ref();
|
||||
ref.setGroup("content.halo.run");
|
||||
ref.setVersion("v1alpha1");
|
||||
ref.setKind(Post.KIND);
|
||||
ref.setName(name);
|
||||
return ref;
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishContentWhenHasPublishedThenDoNothing() {
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, "test-post");
|
||||
final Ref ref = postRef("test-post");
|
||||
|
||||
// v1(released),v2
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV1.getMetadata().getLabels());
|
||||
snapshotV1.getSpec().setPublishTime(Instant.now());
|
||||
snapshotV1.getSpec().setSubjectRef(subjectRef);
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
|
||||
final String headSnapshot = snapshotV1.getMetadata().getName();
|
||||
|
||||
|
@ -252,7 +277,7 @@ class ContentServiceTest {
|
|||
|
||||
when(client.update(any())).thenReturn(Mono.just(snapshotV2()));
|
||||
|
||||
StepVerifier.create(contentService.publish(headSnapshot, subjectRef))
|
||||
StepVerifier.create(contentService.publish(headSnapshot, ref))
|
||||
.expectNext()
|
||||
.consumeNextWith(p -> {
|
||||
System.out.println(JsonUtils.objectToJson(p));
|
||||
|
@ -271,21 +296,23 @@ class ContentServiceTest {
|
|||
@Test
|
||||
void baseSnapshotVersion() {
|
||||
String postName = "post-1";
|
||||
final Ref ref = postRef(postName);
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV1.getMetadata().getLabels());
|
||||
snapshotV1.getSpec().setPublishTime(Instant.now());
|
||||
snapshotV1.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV2.getSpec().setSubjectRef(ref);
|
||||
|
||||
Snapshot snapshotV3 = TestPost.snapshotV3();
|
||||
snapshotV3.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV3.getSpec().setSubjectRef(ref);
|
||||
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV2, snapshotV1, snapshotV3));
|
||||
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, postName);
|
||||
StepVerifier.create(contentService.getBaseSnapshot(subjectRef))
|
||||
StepVerifier.create(contentService.getBaseSnapshot(ref))
|
||||
.expectNext(snapshotV1)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
@ -294,24 +321,26 @@ class ContentServiceTest {
|
|||
@Test
|
||||
void latestSnapshotVersion() {
|
||||
String postName = "post-1";
|
||||
final Ref ref = postRef(postName);
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV1.getMetadata().getLabels());
|
||||
snapshotV1.getSpec().setPublishTime(Instant.now());
|
||||
snapshotV1.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV2.getSpec().setSubjectRef(ref);
|
||||
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2));
|
||||
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, postName);
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(subjectRef))
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(ref))
|
||||
.expectNext(snapshotV2)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2, snapshotV3()));
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(subjectRef))
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(ref))
|
||||
.expectNext(snapshotV3())
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
@ -320,19 +349,21 @@ class ContentServiceTest {
|
|||
@Test
|
||||
void latestPublishedSnapshotThenV1() {
|
||||
String postName = "post-1";
|
||||
Ref ref = postRef(postName);
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV1.getMetadata().getLabels());
|
||||
snapshotV1.getSpec().setPublishTime(Instant.now());
|
||||
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV2.getSpec().setSubjectRef(ref);
|
||||
snapshotV2.getSpec().setPublishTime(null);
|
||||
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2));
|
||||
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, postName);
|
||||
StepVerifier.create(contentService.latestPublishedSnapshot(subjectRef))
|
||||
StepVerifier.create(contentService.latestPublishedSnapshot(ref))
|
||||
.expectNext(snapshotV1)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
@ -341,20 +372,23 @@ class ContentServiceTest {
|
|||
@Test
|
||||
void latestPublishedSnapshotThenV2() {
|
||||
String postName = "post-1";
|
||||
Ref ref = postRef(postName);
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV1.getMetadata().getLabels());
|
||||
snapshotV1.getSpec().setPublishTime(Instant.now());
|
||||
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.setSubjectRef(Post.KIND, postName);
|
||||
snapshotV2.getSpec().setSubjectRef(ref);
|
||||
snapshotV2.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV2.getMetadata().getLabels());
|
||||
snapshotV2.getSpec().setPublishTime(Instant.now());
|
||||
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV2, snapshotV1));
|
||||
|
||||
Snapshot.SubjectRef subjectRef = Snapshot.SubjectRef.of(Post.KIND, postName);
|
||||
|
||||
StepVerifier.create(contentService.latestPublishedSnapshot(subjectRef))
|
||||
StepVerifier.create(contentService.latestPublishedSnapshot(ref))
|
||||
.expectNext(snapshotV2)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
|
|
@ -2,21 +2,37 @@ package run.halo.app.content.impl;
|
|||
|
||||
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.mockito.stubbing.Answer;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.PostQuery;
|
||||
import run.halo.app.content.TestPost;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.infra.Condition;
|
||||
import run.halo.app.infra.ConditionStatus;
|
||||
|
||||
/**
|
||||
* Tests for {@link PostServiceImpl}.
|
||||
|
@ -74,4 +90,124 @@ class PostServiceImplTest {
|
|||
test = postService.postListPredicate(postQuery).test(post);
|
||||
assertThat(test).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishWhenPostIsNonePublishedState() {
|
||||
String postName = "fake-post";
|
||||
String snapV1name = "fake-post-snapshot-v1";
|
||||
Post post = TestPost.postV1();
|
||||
post.getMetadata().setName(postName);
|
||||
|
||||
// v1 not published
|
||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
snapshotV1.getMetadata().setName(snapV1name);
|
||||
snapshotV1.getSpec().setPublishTime(null);
|
||||
post.getSpec().setBaseSnapshot(snapshotV1.getMetadata().getName());
|
||||
|
||||
post.getSpec().setHeadSnapshot(null);
|
||||
post.getSpec().setReleaseSnapshot(null);
|
||||
when(client.fetch(eq(Post.class), eq(postName))).thenReturn(Mono.just(post));
|
||||
verify(client, times(0)).fetch(eq(Snapshot.class), eq(snapV1name));
|
||||
|
||||
postService.publishPost(postName)
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishWhenPostIsPublishedStateAndNotPublishedBefore() {
|
||||
String postName = "fake-post";
|
||||
String snapV1name = "fake-post-snapshot-v1";
|
||||
Post post = TestPost.postV1();
|
||||
post.getSpec().setPublish(true);
|
||||
post.getSpec().setPublishTime(null);
|
||||
post.getMetadata().setName(postName);
|
||||
post.getSpec().setBaseSnapshot(snapV1name);
|
||||
post.getSpec().setHeadSnapshot(null);
|
||||
post.getSpec().setReleaseSnapshot(null);
|
||||
when(client.fetch(eq(Post.class), eq(postName))).thenReturn(Mono.just(post));
|
||||
|
||||
// v1 not published
|
||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
snapshotV1.getMetadata().setName(snapV1name);
|
||||
snapshotV1.getSpec().setPublishTime(null);
|
||||
when(client.fetch(eq(Snapshot.class), eq(snapV1name))).thenReturn(Mono.just(snapshotV1));
|
||||
|
||||
when(contentService.publish(eq(snapV1name), eq(Ref.of(post))))
|
||||
.thenReturn(Mono.just(snapshotV1.applyPatch(snapshotV1)));
|
||||
|
||||
when(client.update(any(Post.class))).thenAnswer((Answer<Mono<Post>>) invocation -> {
|
||||
Post updated = invocation.getArgument(0);
|
||||
return Mono.just(updated);
|
||||
});
|
||||
|
||||
postService.publishPost(postName)
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(expected -> {
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
verify(client, times(1)).update(captor.capture());
|
||||
Post updated = captor.getValue();
|
||||
assertThat(updated.getSpec().getReleaseSnapshot()).isEqualTo(snapV1name);
|
||||
assertThat(updated.getSpec().getHeadSnapshot()).isEqualTo(snapV1name);
|
||||
assertThat(updated.getSpec().getPublishTime()).isNotNull();
|
||||
assertThat(updated.getSpec().getVersion()).isEqualTo(1);
|
||||
List<Condition> conditions = updated.getStatus().getConditions();
|
||||
assertThat(conditions).hasSize(1);
|
||||
assertThat(conditions.get(0).getType()).isEqualTo("PUBLISHED");
|
||||
assertThat(conditions.get(0).getStatus()).isEqualTo(ConditionStatus.TRUE);
|
||||
assertThat(expected).isNotNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishWhenPostIsPublishedStateAndPublishedBefore() {
|
||||
String postName = "fake-post";
|
||||
String snapV1name = "fake-post-snapshot-v1";
|
||||
String snapV2name = "fake-post-snapshot-v2";
|
||||
Post post = TestPost.postV1();
|
||||
post.getSpec().setPublish(true);
|
||||
post.getSpec().setPublishTime(null);
|
||||
post.getMetadata().setName(postName);
|
||||
post.getSpec().setBaseSnapshot(snapV1name);
|
||||
post.getSpec().setHeadSnapshot(snapV2name);
|
||||
post.getSpec().setReleaseSnapshot(snapV2name);
|
||||
ExtensionUtil.nullSafeLabels(post).put(Post.PUBLISHED_LABEL, "true");
|
||||
when(client.fetch(eq(Post.class), eq(postName))).thenReturn(Mono.just(post));
|
||||
|
||||
// v1 has been published
|
||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
snapshotV1.getMetadata().setName(snapV1name);
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV1.getMetadata().getLabels());
|
||||
snapshotV1.getSpec().setPublishTime(Instant.now());
|
||||
|
||||
// v1 not published
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.getMetadata().setName(snapV2name);
|
||||
snapshotV2.getSpec().setPublishTime(null);
|
||||
when(client.fetch(eq(Snapshot.class), eq(snapV2name))).thenReturn(Mono.just(snapshotV2));
|
||||
|
||||
when(contentService.publish(eq(snapV2name), eq(Ref.of(post))))
|
||||
.thenReturn(Mono.just(snapshotV2.applyPatch(snapshotV1)));
|
||||
|
||||
when(client.update(any(Post.class))).thenAnswer((Answer<Mono<Post>>) invocation -> {
|
||||
Post updated = invocation.getArgument(0);
|
||||
return Mono.just(updated);
|
||||
});
|
||||
|
||||
postService.publishPost(postName)
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(expected -> {
|
||||
assertThat(expected).isNotNull();
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
verify(client, times(1)).update(captor.capture());
|
||||
Post updated = captor.getValue();
|
||||
assertThat(updated.getSpec().getReleaseSnapshot()).isEqualTo(snapV2name);
|
||||
assertThat(updated.getSpec().getHeadSnapshot()).isEqualTo(snapV2name);
|
||||
assertThat(updated.getSpec().getPublishTime()).isNotNull();
|
||||
assertThat(updated.getSpec().getVersion()).isEqualTo(2);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
|
@ -2,11 +2,13 @@ package run.halo.app.core.extension.endpoint;
|
|||
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
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;
|
||||
|
@ -15,6 +17,7 @@ import run.halo.app.content.PostRequest;
|
|||
import run.halo.app.content.PostService;
|
||||
import run.halo.app.content.TestPost;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
|
||||
/**
|
||||
* Tests for @{@link PostEndpoint}.
|
||||
|
@ -26,13 +29,16 @@ import run.halo.app.core.extension.Post;
|
|||
class PostEndpointTest {
|
||||
@Mock
|
||||
private PostService postService;
|
||||
@Mock
|
||||
private ReactiveExtensionClient client;
|
||||
|
||||
@InjectMocks
|
||||
private PostEndpoint postEndpoint;
|
||||
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
PostEndpoint postEndpoint = new PostEndpoint(postService);
|
||||
|
||||
webTestClient = WebTestClient
|
||||
.bindToRouterFunction(postEndpoint.endpoint())
|
||||
.build();
|
||||
|
@ -41,7 +47,6 @@ class PostEndpointTest {
|
|||
@Test
|
||||
void draftPost() {
|
||||
when(postService.draftPost(any())).thenReturn(Mono.just(TestPost.postV1()));
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/posts")
|
||||
.bodyValue(postRequest(TestPost.postV1()))
|
||||
|
@ -68,7 +73,11 @@ class PostEndpointTest {
|
|||
|
||||
@Test
|
||||
void publishPost() {
|
||||
when(postService.publishPost(any())).thenReturn(Mono.just(TestPost.postV1()));
|
||||
Post post = TestPost.postV1();
|
||||
when(postService.publishPost(any())).thenReturn(Mono.just(post));
|
||||
when(client.fetch(eq(Post.class), eq(post.getMetadata().getName())))
|
||||
.thenReturn(Mono.just(post));
|
||||
when(client.update(any())).thenReturn(Mono.just(post));
|
||||
|
||||
webTestClient.put()
|
||||
.uri("/posts/post-A/publish")
|
||||
|
@ -77,7 +86,7 @@ class PostEndpointTest {
|
|||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Post.class)
|
||||
.value(post -> assertThat(post).isEqualTo(TestPost.postV1()));
|
||||
.value(p -> assertThat(p).isEqualTo(post));
|
||||
}
|
||||
|
||||
PostRequest postRequest(Post post) {
|
||||
|
|
|
@ -8,6 +8,8 @@ import static org.mockito.Mockito.verify;
|
|||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
@ -21,12 +23,14 @@ import reactor.core.publisher.Flux;
|
|||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.PostService;
|
||||
import run.halo.app.content.TestPost;
|
||||
import run.halo.app.content.permalinks.PostPermalinkPolicy;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.infra.Condition;
|
||||
|
||||
/**
|
||||
* Tests for {@link PostReconciler}.
|
||||
|
@ -44,6 +48,9 @@ class PostReconcilerTest {
|
|||
@Mock
|
||||
private PostPermalinkPolicy postPermalinkPolicy;
|
||||
|
||||
@Mock
|
||||
private PostService postService;
|
||||
|
||||
@InjectMocks
|
||||
private PostReconciler postReconciler;
|
||||
|
||||
|
@ -51,12 +58,13 @@ class PostReconcilerTest {
|
|||
void reconcile() {
|
||||
String name = "post-A";
|
||||
Post post = TestPost.postV1();
|
||||
post.getSpec().setPublished(false);
|
||||
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.empty());
|
||||
when(postService.publishPost(eq(name))).thenReturn(Mono.empty());
|
||||
|
||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
|
@ -85,23 +93,31 @@ class PostReconcilerTest {
|
|||
// https://github.com/halo-dev/halo/issues/2452
|
||||
String name = "post-A";
|
||||
Post post = TestPost.postV1();
|
||||
post.getSpec().setPublished(true);
|
||||
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(
|
||||
new ContentWrapper(post.getSpec().getHeadSnapshot(), "hello world",
|
||||
"<p>hello world</p>", "markdown")));
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(post.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
.content("<p>hello world</p>")
|
||||
.rawType("markdown")
|
||||
.build()));
|
||||
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.getMetadata().setLabels(new HashMap<>());
|
||||
Snapshot.putPublishedLabel(snapshotV2.getMetadata().getLabels());
|
||||
snapshotV2.getSpec().setPublishTime(Instant.now());
|
||||
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
|
||||
|
||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.getSpec().setPublishTime(Instant.now());
|
||||
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
||||
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2));
|
||||
when(postService.publishPost(eq(name))).thenReturn(Mono.empty());
|
||||
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
postReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
@ -111,4 +127,26 @@ class PostReconcilerTest {
|
|||
assertThat(value.getStatus().getExcerpt()).isEqualTo("hello world");
|
||||
}
|
||||
|
||||
@Test
|
||||
void limitConditionSize() {
|
||||
List<Condition> conditions = new ArrayList<>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Condition condition = new Condition();
|
||||
condition.setType("test-" + i);
|
||||
conditions.add(condition);
|
||||
}
|
||||
List<Condition> subConditions = PostReconciler.limitConditionSize(conditions);
|
||||
assertThat(subConditions.get(0).getType()).isEqualTo("test-0");
|
||||
assertThat(subConditions.get(9).getType()).isEqualTo("test-9");
|
||||
|
||||
for (int i = 10; i < 15; i++) {
|
||||
Condition condition = new Condition();
|
||||
condition.setType("test-" + i);
|
||||
conditions.add(condition);
|
||||
}
|
||||
subConditions = PostReconciler.limitConditionSize(conditions);
|
||||
assertThat(subConditions.size()).isEqualTo(10);
|
||||
assertThat(subConditions.get(0).getType()).isEqualTo("test-5");
|
||||
assertThat(subConditions.get(9).getType()).isEqualTo("test-14");
|
||||
}
|
||||
}
|
|
@ -13,10 +13,10 @@ import java.net.URI;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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;
|
||||
|
@ -24,6 +24,7 @@ import reactor.core.publisher.Flux;
|
|||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.SinglePageService;
|
||||
import run.halo.app.content.TestPost;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.SinglePage;
|
||||
|
@ -56,17 +57,15 @@ class SinglePageReconcilerTest {
|
|||
@Mock
|
||||
private CounterService counterService;
|
||||
|
||||
@Mock
|
||||
private SinglePageService singlePageService;
|
||||
|
||||
@Mock
|
||||
private ExternalUrlSupplier externalUrlSupplier;
|
||||
|
||||
@InjectMocks
|
||||
private SinglePageReconciler singlePageReconciler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
singlePageReconciler = new SinglePageReconciler(client, contentService, applicationContext,
|
||||
counterService, externalUrlSupplier);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconcile() {
|
||||
String name = "page-A";
|
||||
|
@ -75,9 +74,13 @@ class SinglePageReconcilerTest {
|
|||
when(client.fetch(eq(SinglePage.class), eq(name)))
|
||||
.thenReturn(Optional.of(page));
|
||||
when(contentService.getContent(eq(page.getSpec().getHeadSnapshot())))
|
||||
.thenReturn(Mono.just(
|
||||
new ContentWrapper(page.getSpec().getHeadSnapshot(), "hello world",
|
||||
"<p>hello world</p>", "markdown")));
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(page.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
.content("<p>hello world</p>")
|
||||
.rawType("markdown")
|
||||
.build())
|
||||
);
|
||||
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
|
@ -86,6 +89,7 @@ class SinglePageReconcilerTest {
|
|||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2));
|
||||
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
|
||||
when(singlePageService.publish(eq(name))).thenReturn(Mono.empty());
|
||||
|
||||
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
||||
singlePageReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
|
|
@ -8,6 +8,7 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
@ -67,14 +68,19 @@ class PostFinderImplTest {
|
|||
void content() {
|
||||
Post post = post(1);
|
||||
post.getSpec().setReleaseSnapshot("release-snapshot");
|
||||
ContentWrapper contentWrapper = new ContentWrapper("snapshot", "raw", "content", "rawType");
|
||||
ContentWrapper contentWrapper = ContentWrapper.builder()
|
||||
.snapshotName("snapshot")
|
||||
.raw("raw")
|
||||
.content("content")
|
||||
.rawType("rawType")
|
||||
.build();
|
||||
when(client.fetch(eq(Post.class), eq("post-1")))
|
||||
.thenReturn(Mono.just(post));
|
||||
when(contentService.getContent(post.getSpec().getReleaseSnapshot()))
|
||||
.thenReturn(Mono.just(contentWrapper));
|
||||
ContentVo content = postFinder.content("post-1");
|
||||
assertThat(content.getContent()).isEqualTo(contentWrapper.content());
|
||||
assertThat(content.getRaw()).isEqualTo(contentWrapper.raw());
|
||||
assertThat(content.getContent()).isEqualTo(contentWrapper.getContent());
|
||||
assertThat(content.getRaw()).isEqualTo(contentWrapper.getRaw());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -166,17 +172,17 @@ class PostFinderImplTest {
|
|||
|
||||
List<Post> postsForArchives() {
|
||||
Post post1 = post(1);
|
||||
post1.getSpec().setPublished(true);
|
||||
post1.getSpec().setPublish(true);
|
||||
post1.getSpec().setPublishTime(Instant.parse("2021-01-01T00:00:00Z"));
|
||||
post1.getMetadata().setCreationTimestamp(Instant.now());
|
||||
|
||||
Post post2 = post(2);
|
||||
post2.getSpec().setPublished(true);
|
||||
post2.getSpec().setPublish(true);
|
||||
post2.getSpec().setPublishTime(Instant.parse("2022-12-01T00:00:00Z"));
|
||||
post2.getMetadata().setCreationTimestamp(Instant.now());
|
||||
|
||||
Post post3 = post(3);
|
||||
post3.getSpec().setPublished(true);
|
||||
post3.getSpec().setPublish(true);
|
||||
post3.getSpec().setPublishTime(Instant.parse("2022-12-03T00:00:00Z"));
|
||||
post3.getMetadata().setCreationTimestamp(Instant.now());
|
||||
return List.of(post1, post2, post3);
|
||||
|
@ -205,7 +211,8 @@ class PostFinderImplTest {
|
|||
post4.getMetadata().setCreationTimestamp(Instant.now());
|
||||
|
||||
Post post5 = post(5);
|
||||
post5.getSpec().setPublished(false);
|
||||
post5.getSpec().setPublish(false);
|
||||
post5.getMetadata().getLabels().clear();
|
||||
post5.getMetadata().setCreationTimestamp(Instant.now());
|
||||
|
||||
Post post6 = post(6);
|
||||
|
@ -222,6 +229,8 @@ class PostFinderImplTest {
|
|||
metadata.setName("post-" + i);
|
||||
metadata.setCreationTimestamp(Instant.now());
|
||||
metadata.setAnnotations(Map.of("K1", "V1"));
|
||||
metadata.setLabels(new HashMap<>());
|
||||
metadata.getLabels().put(Post.PUBLISHED_LABEL, "true");
|
||||
post.setMetadata(metadata);
|
||||
|
||||
Post.PostSpec postSpec = new Post.PostSpec();
|
||||
|
@ -230,7 +239,7 @@ class PostFinderImplTest {
|
|||
postSpec.setPublishTime(Instant.now());
|
||||
postSpec.setPinned(false);
|
||||
postSpec.setPriority(0);
|
||||
postSpec.setPublished(true);
|
||||
postSpec.setPublish(true);
|
||||
postSpec.setVisible(Post.VisibleEnum.PUBLIC);
|
||||
postSpec.setTitle("title-" + i);
|
||||
postSpec.setSlug("slug-" + i);
|
||||
|
|
Loading…
Reference in New Issue