mirror of https://github.com/halo-dev/halo
perf: optimize snapshot queries for post and single page (#3168)
#### What type of PR is this? /kind improvement /area core /milestone 2.2.x /kind api-change #### What this PR does / why we need it: 优化文章和自定义页面的内容查询 - 文章新增 `GET /posts/{name}/release-content` 和 `GET /posts/{name}/head-content` 自定义 APIs - 自定义页面新增 `GET /posts/{name}/release-content` 和 `GET /posts/{name}/head-content` 自定义 APIs ⚠️ 移除了 ContentEndpoint,如果 Console 调用过 `/apis/api.console.halo.run/contents/{name}` 查询内容则需要用文章或自定义页面的自定义APIs 替代 #### Which issue(s) this PR fixes: Fixes #3140 Fixes https://github.com/halo-dev/halo/issues/3026 #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note 优化文章和自定义页面的内容查询 ```pull/3185/head
parent
1d4f65c0cf
commit
3bd0a09764
|
@ -0,0 +1,142 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Snapshot;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
|
||||
/**
|
||||
* Abstract Service for {@link Snapshot}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public abstract class AbstractContentService {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
public Mono<ContentWrapper> getContent(String snapshotName, String baseSnapshotName) {
|
||||
return client.fetch(Snapshot.class, baseSnapshotName)
|
||||
.doOnNext(this::checkBaseSnapshot)
|
||||
.flatMap(baseSnapshot -> {
|
||||
if (StringUtils.equals(snapshotName, baseSnapshotName)) {
|
||||
return Mono.just(baseSnapshot.applyPatch(baseSnapshot));
|
||||
}
|
||||
return client.fetch(Snapshot.class, snapshotName)
|
||||
.map(snapshot -> snapshot.applyPatch(baseSnapshot));
|
||||
});
|
||||
}
|
||||
|
||||
protected void checkBaseSnapshot(Snapshot snapshot) {
|
||||
Assert.notNull(snapshot, "The snapshot must not be null.");
|
||||
String keepRawAnno =
|
||||
ExtensionUtil.nullSafeAnnotations(snapshot).get(Snapshot.KEEP_RAW_ANNO);
|
||||
if (!org.thymeleaf.util.StringUtils.equals(Boolean.TRUE.toString(), keepRawAnno)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("The snapshot [%s] is not a base snapshot.",
|
||||
snapshot.getMetadata().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
protected Mono<ContentWrapper> draftContent(@Nullable String baseSnapshotName,
|
||||
ContentRequest contentRequest,
|
||||
@Nullable String parentSnapshotName) {
|
||||
Snapshot snapshot = contentRequest.toSnapshot();
|
||||
snapshot.getMetadata().setName(UUID.randomUUID().toString());
|
||||
snapshot.getSpec().setParentSnapshotName(parentSnapshotName);
|
||||
|
||||
final String baseSnapshotNameToUse =
|
||||
StringUtils.defaultString(baseSnapshotName, snapshot.getMetadata().getName());
|
||||
return client.fetch(Snapshot.class, baseSnapshotName)
|
||||
.doOnNext(this::checkBaseSnapshot)
|
||||
.defaultIfEmpty(snapshot)
|
||||
.map(baseSnapshot -> determineRawAndContentPatch(snapshot, baseSnapshot,
|
||||
contentRequest)
|
||||
)
|
||||
.flatMap(source -> getContextUsername()
|
||||
.map(username -> {
|
||||
Snapshot.addContributor(source, username);
|
||||
source.getSpec().setOwner(username);
|
||||
return source;
|
||||
})
|
||||
.defaultIfEmpty(source)
|
||||
)
|
||||
.flatMap(snapshotToCreate -> client.create(snapshotToCreate)
|
||||
.flatMap(head -> restoredContent(baseSnapshotNameToUse, head)));
|
||||
}
|
||||
|
||||
protected Mono<ContentWrapper> draftContent(String baseSnapshotName, ContentRequest content) {
|
||||
return this.draftContent(baseSnapshotName, content, content.headSnapshotName());
|
||||
}
|
||||
|
||||
protected Mono<ContentWrapper> updateContent(String baseSnapshotName,
|
||||
ContentRequest contentRequest) {
|
||||
Assert.notNull(contentRequest, "The contentRequest must not be null");
|
||||
Assert.notNull(baseSnapshotName, "The baseSnapshotName must not be null");
|
||||
Assert.notNull(contentRequest.headSnapshotName(), "The headSnapshotName must not be null");
|
||||
return client.fetch(Snapshot.class, contentRequest.headSnapshotName())
|
||||
.flatMap(headSnapshot -> client.fetch(Snapshot.class, baseSnapshotName)
|
||||
.map(baseSnapshot -> determineRawAndContentPatch(headSnapshot, baseSnapshot,
|
||||
contentRequest)
|
||||
)
|
||||
)
|
||||
.flatMap(headSnapshot -> getContextUsername()
|
||||
.map(username -> {
|
||||
Snapshot.addContributor(headSnapshot, username);
|
||||
return headSnapshot;
|
||||
})
|
||||
.defaultIfEmpty(headSnapshot)
|
||||
)
|
||||
.flatMap(client::update)
|
||||
.flatMap(head -> restoredContent(baseSnapshotName, head));
|
||||
}
|
||||
|
||||
protected Mono<ContentWrapper> restoredContent(String baseSnapshotName, Snapshot headSnapshot) {
|
||||
return client.fetch(Snapshot.class, baseSnapshotName)
|
||||
.doOnNext(this::checkBaseSnapshot)
|
||||
.map(headSnapshot::applyPatch);
|
||||
}
|
||||
|
||||
protected Snapshot determineRawAndContentPatch(Snapshot snapshotToUse, Snapshot baseSnapshot,
|
||||
ContentRequest contentRequest) {
|
||||
Assert.notNull(baseSnapshot, "The baseSnapshot must not be null.");
|
||||
Assert.notNull(contentRequest, "The contentRequest must not be null.");
|
||||
Assert.notNull(snapshotToUse, "The snapshotToUse not be null.");
|
||||
String originalRaw = baseSnapshot.getSpec().getRawPatch();
|
||||
String originalContent = baseSnapshot.getSpec().getContentPatch();
|
||||
String baseSnapshotName = baseSnapshot.getMetadata().getName();
|
||||
|
||||
snapshotToUse.getSpec().setLastModifyTime(Instant.now());
|
||||
// it is the v1 snapshot, set the content directly
|
||||
if (org.thymeleaf.util.StringUtils.equals(baseSnapshotName,
|
||||
snapshotToUse.getMetadata().getName())) {
|
||||
snapshotToUse.getSpec().setRawPatch(contentRequest.raw());
|
||||
snapshotToUse.getSpec().setContentPatch(contentRequest.content());
|
||||
ExtensionUtil.nullSafeAnnotations(snapshotToUse)
|
||||
.put(Snapshot.KEEP_RAW_ANNO, Boolean.TRUE.toString());
|
||||
} else {
|
||||
// otherwise diff a patch based on the v1 snapshot
|
||||
String revisedRaw = contentRequest.rawPatchFrom(originalRaw);
|
||||
String revisedContent = contentRequest.contentPatchFrom(originalContent);
|
||||
snapshotToUse.getSpec().setRawPatch(revisedRaw);
|
||||
snapshotToUse.getSpec().setContentPatch(revisedContent);
|
||||
}
|
||||
return snapshotToUse;
|
||||
}
|
||||
|
||||
protected Mono<String> getContextUsername() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Principal::getName);
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.content.Snapshot;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* Content service for {@link Snapshot}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface ContentService {
|
||||
|
||||
Mono<ContentWrapper> getContent(String name);
|
||||
|
||||
Mono<ContentWrapper> draftContent(ContentRequest content);
|
||||
|
||||
Mono<ContentWrapper> draftContent(ContentRequest content, String parentName);
|
||||
|
||||
Mono<ContentWrapper> updateContent(ContentRequest content);
|
||||
|
||||
Mono<Snapshot> getBaseSnapshot(Ref subjectRef);
|
||||
|
||||
Mono<Snapshot> latestSnapshotVersion(Ref subjectRef);
|
||||
|
||||
Flux<Snapshot> listSnapshots(Ref subjectRef);
|
||||
}
|
|
@ -17,4 +17,10 @@ public interface PostService {
|
|||
Mono<Post> draftPost(PostRequest postRequest);
|
||||
|
||||
Mono<Post> updatePost(PostRequest postRequest);
|
||||
|
||||
Mono<ContentWrapper> getHeadContent(String postName);
|
||||
|
||||
Mono<ContentWrapper> getReleaseContent(String postName);
|
||||
|
||||
Mono<ContentWrapper> getContent(String snapshotName, String baseSnapshotName);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,12 @@ import run.halo.app.extension.ListResult;
|
|||
*/
|
||||
public interface SinglePageService {
|
||||
|
||||
Mono<ContentWrapper> getHeadContent(String singlePageName);
|
||||
|
||||
Mono<ContentWrapper> getReleaseContent(String singlePageName);
|
||||
|
||||
Mono<ContentWrapper> getContent(String snapshotName, String baseSnapshotName);
|
||||
|
||||
Mono<ListResult<ListedSinglePage>> list(SinglePageQuery listRequest);
|
||||
|
||||
Mono<SinglePage> draft(SinglePageRequest pageRequest);
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
package run.halo.app.content.impl;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.thymeleaf.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
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.content.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}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
public class ContentServiceImpl implements ContentService {
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
public ContentServiceImpl(ReactiveExtensionClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ContentWrapper> getContent(String name) {
|
||||
return client.fetch(Snapshot.class, name)
|
||||
.flatMap(snapshot -> getBaseSnapshot(snapshot.getSpec().getSubjectRef())
|
||||
.map(snapshot::applyPatch));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ContentWrapper> draftContent(ContentRequest content) {
|
||||
return this.draftContent(content, content.headSnapshotName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ContentWrapper> draftContent(ContentRequest contentRequest, String parentName) {
|
||||
return Mono.defer(
|
||||
() -> {
|
||||
Snapshot snapshot = contentRequest.toSnapshot();
|
||||
snapshot.getMetadata().setName(UUID.randomUUID().toString());
|
||||
snapshot.getSpec().setParentSnapshotName(parentName);
|
||||
return getBaseSnapshot(contentRequest.subjectRef())
|
||||
.defaultIfEmpty(snapshot)
|
||||
.map(baseSnapshot -> determineRawAndContentPatch(snapshot, baseSnapshot,
|
||||
contentRequest))
|
||||
.flatMap(source -> getContextUsername()
|
||||
.map(username -> {
|
||||
Snapshot.addContributor(source, username);
|
||||
source.getSpec().setOwner(username);
|
||||
return source;
|
||||
})
|
||||
.defaultIfEmpty(source)
|
||||
);
|
||||
})
|
||||
.flatMap(snapshot -> client.create(snapshot)
|
||||
.flatMap(this::restoredContent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ContentWrapper> updateContent(ContentRequest contentRequest) {
|
||||
Assert.notNull(contentRequest, "The contentRequest must not be null");
|
||||
Assert.notNull(contentRequest.headSnapshotName(), "The headSnapshotName must not be null");
|
||||
Ref subjectRef = contentRequest.subjectRef();
|
||||
return client.fetch(Snapshot.class, contentRequest.headSnapshotName())
|
||||
.flatMap(headSnapshot -> getBaseSnapshot(subjectRef)
|
||||
.map(baseSnapshot -> determineRawAndContentPatch(headSnapshot, baseSnapshot,
|
||||
contentRequest)
|
||||
)
|
||||
)
|
||||
.flatMap(headSnapshot -> getContextUsername()
|
||||
.map(username -> {
|
||||
Snapshot.addContributor(headSnapshot, username);
|
||||
return headSnapshot;
|
||||
})
|
||||
.defaultIfEmpty(headSnapshot)
|
||||
)
|
||||
.flatMap(client::update)
|
||||
.flatMap(this::restoredContent);
|
||||
}
|
||||
|
||||
private Mono<ContentWrapper> restoredContent(Snapshot headSnapshot) {
|
||||
return getBaseSnapshot(headSnapshot.getSpec().getSubjectRef())
|
||||
.map(headSnapshot::applyPatch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Snapshot> getBaseSnapshot(Ref subjectRef) {
|
||||
return listSnapshots(subjectRef)
|
||||
.sort(createTimeReversedComparator().reversed())
|
||||
.filter(p -> StringUtils.equals(Boolean.TRUE.toString(),
|
||||
ExtensionUtil.nullSafeAnnotations(p).get(Snapshot.KEEP_RAW_ANNO)))
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Snapshot> latestSnapshotVersion(Ref subjectRef) {
|
||||
Assert.notNull(subjectRef, "The subjectRef must not be null.");
|
||||
return listSnapshots(subjectRef)
|
||||
.sort(createTimeReversedComparator())
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
private Mono<String> getContextUsername() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Principal::getName);
|
||||
}
|
||||
|
||||
private Snapshot determineRawAndContentPatch(Snapshot snapshotToUse, Snapshot baseSnapshot,
|
||||
ContentRequest contentRequest) {
|
||||
Assert.notNull(baseSnapshot, "The baseSnapshot must not be null.");
|
||||
Assert.notNull(contentRequest, "The contentRequest must not be null.");
|
||||
Assert.notNull(snapshotToUse, "The snapshotToUse not be null.");
|
||||
String originalRaw = baseSnapshot.getSpec().getRawPatch();
|
||||
String originalContent = baseSnapshot.getSpec().getContentPatch();
|
||||
String baseSnapshotName = baseSnapshot.getMetadata().getName();
|
||||
|
||||
snapshotToUse.getSpec().setLastModifyTime(Instant.now());
|
||||
// it is the v1 snapshot, set the content directly
|
||||
if (StringUtils.equals(baseSnapshotName, snapshotToUse.getMetadata().getName())) {
|
||||
snapshotToUse.getSpec().setRawPatch(contentRequest.raw());
|
||||
snapshotToUse.getSpec().setContentPatch(contentRequest.content());
|
||||
ExtensionUtil.nullSafeAnnotations(snapshotToUse)
|
||||
.put(Snapshot.KEEP_RAW_ANNO, Boolean.TRUE.toString());
|
||||
} else {
|
||||
// otherwise diff a patch based on the v1 snapshot
|
||||
String revisedRaw = contentRequest.rawPatchFrom(originalRaw);
|
||||
String revisedContent = contentRequest.contentPatchFrom(originalContent);
|
||||
snapshotToUse.getSpec().setRawPatch(revisedRaw);
|
||||
snapshotToUse.getSpec().setContentPatch(revisedContent);
|
||||
}
|
||||
return snapshotToUse;
|
||||
}
|
||||
|
||||
Comparator<Snapshot> createTimeReversedComparator() {
|
||||
Function<Snapshot, String> name = snapshot -> snapshot.getMetadata().getName();
|
||||
Function<Snapshot, Instant> createTime = snapshot -> snapshot.getMetadata()
|
||||
.getCreationTimestamp();
|
||||
return Comparator.comparing(createTime)
|
||||
.thenComparing(name)
|
||||
.reversed();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package run.halo.app.content.impl;
|
|||
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
|
@ -11,19 +10,16 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
import run.halo.app.content.AbstractContentService;
|
||||
import run.halo.app.content.ContentRequest;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.Contributor;
|
||||
import run.halo.app.content.ListedPost;
|
||||
|
@ -52,12 +48,16 @@ import run.halo.app.metrics.MeterUtils;
|
|||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class PostServiceImpl implements PostService {
|
||||
private final ContentService contentService;
|
||||
public class PostServiceImpl extends AbstractContentService implements PostService {
|
||||
private final ReactiveExtensionClient client;
|
||||
private final CounterService counterService;
|
||||
|
||||
public PostServiceImpl(ReactiveExtensionClient client, CounterService counterService) {
|
||||
super(client);
|
||||
this.client = client;
|
||||
this.counterService = counterService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<ListedPost>> listPost(PostQuery query) {
|
||||
Comparator<Post> comparator =
|
||||
|
@ -253,7 +253,7 @@ public class PostServiceImpl implements PostService {
|
|||
new ContentRequest(Ref.of(post), post.getSpec().getHeadSnapshot(),
|
||||
postRequest.content().raw(), postRequest.content().content(),
|
||||
postRequest.content().rawType());
|
||||
return contentService.draftContent(contentRequest)
|
||||
return draftContent(post.getSpec().getBaseSnapshot(), contentRequest)
|
||||
.flatMap(contentWrapper -> waitForPostToDraftConcludingWork(
|
||||
post.getMetadata().getName(),
|
||||
contentWrapper)
|
||||
|
@ -293,16 +293,17 @@ public class PostServiceImpl implements PostService {
|
|||
Post post = postRequest.post();
|
||||
String headSnapshot = post.getSpec().getHeadSnapshot();
|
||||
String releaseSnapshot = post.getSpec().getReleaseSnapshot();
|
||||
String baseSnapshot = post.getSpec().getBaseSnapshot();
|
||||
|
||||
if (StringUtils.equals(releaseSnapshot, headSnapshot)) {
|
||||
// create new snapshot to update first
|
||||
return contentService.draftContent(postRequest.contentRequest(), headSnapshot)
|
||||
return draftContent(baseSnapshot, postRequest.contentRequest(), headSnapshot)
|
||||
.flatMap(contentWrapper -> {
|
||||
post.getSpec().setHeadSnapshot(contentWrapper.getSnapshotName());
|
||||
return client.update(post);
|
||||
});
|
||||
}
|
||||
return contentService.updateContent(postRequest.contentRequest())
|
||||
return updateContent(baseSnapshot, postRequest.contentRequest())
|
||||
.flatMap(contentWrapper -> {
|
||||
post.getSpec().setHeadSnapshot(contentWrapper.getSnapshotName());
|
||||
return client.update(post);
|
||||
|
@ -311,9 +312,21 @@ public class PostServiceImpl implements PostService {
|
|||
.filter(throwable -> throwable instanceof OptimisticLockingFailureException));
|
||||
}
|
||||
|
||||
private Mono<String> getContextUsername() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Principal::getName);
|
||||
@Override
|
||||
public Mono<ContentWrapper> getHeadContent(String postName) {
|
||||
return client.get(Post.class, postName)
|
||||
.flatMap(post -> {
|
||||
String headSnapshot = post.getSpec().getHeadSnapshot();
|
||||
return getContent(headSnapshot, post.getSpec().getBaseSnapshot());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ContentWrapper> getReleaseContent(String postName) {
|
||||
return client.get(Post.class, postName)
|
||||
.flatMap(post -> {
|
||||
String releaseSnapshot = post.getSpec().getReleaseSnapshot();
|
||||
return getContent(releaseSnapshot, post.getSpec().getBaseSnapshot());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package run.halo.app.content.impl;
|
|||
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
|
@ -11,19 +10,16 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
import run.halo.app.content.AbstractContentService;
|
||||
import run.halo.app.content.ContentRequest;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.Contributor;
|
||||
import run.halo.app.content.ListedSinglePage;
|
||||
|
@ -51,14 +47,35 @@ import run.halo.app.metrics.MeterUtils;
|
|||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class SinglePageServiceImpl implements SinglePageService {
|
||||
private final ContentService contentService;
|
||||
public class SinglePageServiceImpl extends AbstractContentService implements SinglePageService {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
private final CounterService counterService;
|
||||
|
||||
public SinglePageServiceImpl(ReactiveExtensionClient client, CounterService counterService) {
|
||||
super(client);
|
||||
this.client = client;
|
||||
this.counterService = counterService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ContentWrapper> getHeadContent(String singlePageName) {
|
||||
return client.get(SinglePage.class, singlePageName)
|
||||
.flatMap(singlePage -> {
|
||||
String headSnapshot = singlePage.getSpec().getHeadSnapshot();
|
||||
return getContent(headSnapshot, singlePage.getSpec().getBaseSnapshot());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ContentWrapper> getReleaseContent(String singlePageName) {
|
||||
return client.get(SinglePage.class, singlePageName)
|
||||
.flatMap(singlePage -> {
|
||||
String releaseSnapshot = singlePage.getSpec().getReleaseSnapshot();
|
||||
return getContent(releaseSnapshot, singlePage.getSpec().getBaseSnapshot());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<ListedSinglePage>> list(SinglePageQuery query) {
|
||||
Comparator<SinglePage> comparator =
|
||||
|
@ -96,7 +113,7 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
new ContentRequest(Ref.of(page), page.getSpec().getHeadSnapshot(),
|
||||
pageRequest.content().raw(), pageRequest.content().content(),
|
||||
pageRequest.content().rawType());
|
||||
return contentService.draftContent(contentRequest)
|
||||
return draftContent(page.getSpec().getBaseSnapshot(), contentRequest)
|
||||
.flatMap(
|
||||
contentWrapper -> waitForPageToDraftConcludingWork(
|
||||
page.getMetadata().getName(),
|
||||
|
@ -137,16 +154,17 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
SinglePage page = pageRequest.page();
|
||||
String headSnapshot = page.getSpec().getHeadSnapshot();
|
||||
String releaseSnapshot = page.getSpec().getReleaseSnapshot();
|
||||
String baseSnapshot = page.getSpec().getBaseSnapshot();
|
||||
|
||||
// create new snapshot to update first
|
||||
if (StringUtils.equals(headSnapshot, releaseSnapshot)) {
|
||||
return contentService.draftContent(pageRequest.contentRequest(), headSnapshot)
|
||||
return draftContent(baseSnapshot, pageRequest.contentRequest(), headSnapshot)
|
||||
.flatMap(contentWrapper -> {
|
||||
page.getSpec().setHeadSnapshot(contentWrapper.getSnapshotName());
|
||||
return client.update(page);
|
||||
});
|
||||
}
|
||||
return contentService.updateContent(pageRequest.contentRequest())
|
||||
return updateContent(baseSnapshot, pageRequest.contentRequest())
|
||||
.flatMap(contentWrapper -> {
|
||||
page.getSpec().setHeadSnapshot(contentWrapper.getSnapshotName());
|
||||
return client.update(page);
|
||||
|
@ -155,12 +173,6 @@ public class SinglePageServiceImpl implements SinglePageService {
|
|||
.filter(throwable -> throwable instanceof OptimisticLockingFailureException));
|
||||
}
|
||||
|
||||
private Mono<String> getContextUsername() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Principal::getName);
|
||||
}
|
||||
|
||||
Predicate<SinglePage> pageListPredicate(SinglePageQuery query) {
|
||||
Predicate<SinglePage> paramPredicate = singlePage -> contains(query.getContributors(),
|
||||
singlePage.getStatusOrDefault().getContributors());
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
package run.halo.app.core.extension.endpoint;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
||||
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;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentRequest;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
|
||||
/**
|
||||
* Endpoint for managing content.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
public class ContentEndpoint implements CustomEndpoint {
|
||||
|
||||
private final ContentService contentService;
|
||||
|
||||
public ContentEndpoint(ContentService contentService) {
|
||||
this.contentService = contentService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = "api.console.halo.run/v1alpha1/Content";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("contents/{snapshotName}", this::obtainContent,
|
||||
builder -> builder.operationId("ObtainSnapshotContent")
|
||||
.description("Obtain a snapshot content.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder()
|
||||
.required(true)
|
||||
.name("snapshotName")
|
||||
.in(ParameterIn.PATH))
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentResponse.class))
|
||||
)
|
||||
.POST("contents", this::draftSnapshotContent,
|
||||
builder -> builder.operationId("DraftSnapshotContent")
|
||||
.description("Draft a snapshot content.")
|
||||
.tag(tag)
|
||||
.requestBody(requestBodyBuilder()
|
||||
.required(true)
|
||||
.content(contentBuilder()
|
||||
.mediaType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.schema(Builder.schemaBuilder()
|
||||
.implementation(ContentRequest.class))
|
||||
))
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentResponse.class))
|
||||
)
|
||||
.PUT("contents/{snapshotName}", this::updateSnapshotContent,
|
||||
builder -> builder.operationId("UpdateSnapshotContent")
|
||||
.description("Update 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(ContentRequest.class))
|
||||
))
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentResponse.class))
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> obtainContent(ServerRequest request) {
|
||||
String snapshotName = request.pathVariable("snapshotName");
|
||||
return contentService.getContent(snapshotName)
|
||||
.map(ContentResponse::from)
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> updateSnapshotContent(ServerRequest request) {
|
||||
String snapshotName = request.pathVariable("snapshotName");
|
||||
return request.bodyToMono(ContentRequest.class)
|
||||
.flatMap(content -> {
|
||||
ContentRequest contentRequest =
|
||||
new ContentRequest(content.subjectRef(), snapshotName,
|
||||
content.raw(), content.content(), content.rawType());
|
||||
return contentService.updateContent(contentRequest);
|
||||
})
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
|||
import org.thymeleaf.util.StringUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.ListedPost;
|
||||
import run.halo.app.content.PostQuery;
|
||||
import run.halo.app.content.PostRequest;
|
||||
|
@ -64,6 +65,30 @@ public class PostEndpoint implements CustomEndpoint {
|
|||
QueryParamBuildUtil.buildParametersFromType(builder, PostQuery.class);
|
||||
}
|
||||
)
|
||||
.GET("posts/{name}/head-content", this::fetchHeadContent,
|
||||
builder -> builder.operationId("fetchPostHeadContent")
|
||||
.description("Fetch head content of post.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder().name("name")
|
||||
.in(ParameterIn.PATH)
|
||||
.required(true)
|
||||
.implementation(String.class)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentWrapper.class))
|
||||
)
|
||||
.GET("posts/{name}/release-content", this::fetchReleaseContent,
|
||||
builder -> builder.operationId("fetchPostReleaseContent")
|
||||
.description("Fetch release content of post.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder().name("name")
|
||||
.in(ParameterIn.PATH)
|
||||
.required(true)
|
||||
.implementation(String.class)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentWrapper.class))
|
||||
)
|
||||
.POST("posts", this::draftPost,
|
||||
builder -> builder.operationId("DraftPost")
|
||||
.description("Draft a post.")
|
||||
|
@ -148,6 +173,18 @@ public class PostEndpoint implements CustomEndpoint {
|
|||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> fetchReleaseContent(ServerRequest request) {
|
||||
final var name = request.pathVariable("name");
|
||||
return postService.getReleaseContent(name)
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> fetchHeadContent(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return postService.getHeadContent(name)
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
Mono<ServerResponse> draftPost(ServerRequest request) {
|
||||
return request.bodyToMono(PostRequest.class)
|
||||
.flatMap(postService::draftPost)
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
|||
import org.thymeleaf.util.StringUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.ListedSinglePage;
|
||||
import run.halo.app.content.SinglePageQuery;
|
||||
import run.halo.app.content.SinglePageRequest;
|
||||
|
@ -59,6 +60,30 @@ public class SinglePageEndpoint implements CustomEndpoint {
|
|||
QueryParamBuildUtil.buildParametersFromType(builder, SinglePageQuery.class);
|
||||
}
|
||||
)
|
||||
.GET("singlepages/{name}/head-content", this::fetchHeadContent,
|
||||
builder -> builder.operationId("fetchSinglePageHeadContent")
|
||||
.description("Fetch head content of single page.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder().name("name")
|
||||
.in(ParameterIn.PATH)
|
||||
.required(true)
|
||||
.implementation(String.class)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentWrapper.class))
|
||||
)
|
||||
.GET("singlepages/{name}/release-content", this::fetchReleaseContent,
|
||||
builder -> builder.operationId("fetchSinglePageReleaseContent")
|
||||
.description("Fetch release content of single page.")
|
||||
.tag(tag)
|
||||
.parameter(parameterBuilder().name("name")
|
||||
.in(ParameterIn.PATH)
|
||||
.required(true)
|
||||
.implementation(String.class)
|
||||
)
|
||||
.response(responseBuilder()
|
||||
.implementation(ContentWrapper.class))
|
||||
)
|
||||
.POST("singlepages", this::draftSinglePage,
|
||||
builder -> builder.operationId("DraftSinglePage")
|
||||
.description("Draft a single page.")
|
||||
|
@ -123,6 +148,18 @@ public class SinglePageEndpoint implements CustomEndpoint {
|
|||
.build();
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> fetchReleaseContent(ServerRequest request) {
|
||||
final var name = request.pathVariable("name");
|
||||
return singlePageService.getReleaseContent(name)
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> fetchHeadContent(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return singlePageService.getHeadContent(name)
|
||||
.flatMap(content -> ServerResponse.ok().bodyValue(content));
|
||||
}
|
||||
|
||||
Mono<ServerResponse> draftSinglePage(ServerRequest request) {
|
||||
return request.bodyToMono(SinglePageRequest.class)
|
||||
.flatMap(singlePageService::draft)
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.jsoup.Jsoup;
|
|||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.PostService;
|
||||
import run.halo.app.content.permalinks.PostPermalinkPolicy;
|
||||
import run.halo.app.core.extension.content.Comment;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
|
@ -52,7 +52,7 @@ import run.halo.app.metrics.MeterUtils;
|
|||
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;
|
||||
|
||||
|
@ -269,7 +269,7 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
spec.setExcerpt(excerpt);
|
||||
}
|
||||
if (excerpt.getAutoGenerate()) {
|
||||
contentService.getContent(spec.getReleaseSnapshot())
|
||||
postService.getContent(spec.getReleaseSnapshot(), spec.getBaseSnapshot())
|
||||
.blockOptional()
|
||||
.ifPresent(content -> {
|
||||
String contentRevised = content.getContent();
|
||||
|
@ -282,11 +282,13 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
Ref ref = Ref.of(post);
|
||||
// handle contributors
|
||||
String headSnapshot = post.getSpec().getHeadSnapshot();
|
||||
contentService.listSnapshots(ref)
|
||||
.collectList()
|
||||
.blockOptional()
|
||||
.ifPresent(snapshots -> {
|
||||
List<String> contributors = snapshots.stream()
|
||||
List<String> contributors = client.list(Snapshot.class,
|
||||
snapshot -> ref.equals(snapshot.getSpec().getSubjectRef()), null)
|
||||
.stream()
|
||||
.peek(snapshot -> {
|
||||
snapshot.getSpec().setContentPatch(StringUtils.EMPTY);
|
||||
snapshot.getSpec().setRawPatch(StringUtils.EMPTY);
|
||||
})
|
||||
.map(snapshot -> {
|
||||
Set<String> usernames = snapshot.getSpec().getContributors();
|
||||
return Objects.requireNonNullElseGet(usernames,
|
||||
|
@ -301,7 +303,6 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
|||
// update in progress status
|
||||
status.setInProgress(
|
||||
!StringUtils.equals(headSnapshot, post.getSpec().getReleaseSnapshot()));
|
||||
});
|
||||
|
||||
if (post.isPublished() && status.getLastModifyTime() == null) {
|
||||
client.fetch(Snapshot.class, post.getSpec().getReleaseSnapshot())
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.jsoup.Jsoup;
|
|||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.SinglePageService;
|
||||
import run.halo.app.content.permalinks.ExtensionLocator;
|
||||
import run.halo.app.core.extension.content.Comment;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
|
@ -60,7 +60,7 @@ 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 SinglePageService singlePageService;
|
||||
private final ApplicationContext applicationContext;
|
||||
private final CounterService counterService;
|
||||
|
||||
|
@ -337,7 +337,7 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
}
|
||||
|
||||
if (excerpt.getAutoGenerate()) {
|
||||
contentService.getContent(spec.getHeadSnapshot())
|
||||
singlePageService.getContent(spec.getHeadSnapshot(), spec.getBaseSnapshot())
|
||||
.blockOptional()
|
||||
.ifPresent(content -> {
|
||||
String contentRevised = content.getContent();
|
||||
|
@ -349,10 +349,13 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
|
||||
// handle contributors
|
||||
String headSnapshot = singlePage.getSpec().getHeadSnapshot();
|
||||
contentService.listSnapshots(Ref.of(singlePage))
|
||||
.collectList()
|
||||
.blockOptional().ifPresent(snapshots -> {
|
||||
List<String> contributors = snapshots.stream()
|
||||
List<String> contributors = client.list(Snapshot.class,
|
||||
snapshot -> Ref.of(singlePage).equals(snapshot.getSpec().getSubjectRef()), null)
|
||||
.stream()
|
||||
.peek(snapshot -> {
|
||||
snapshot.getSpec().setContentPatch(StringUtils.EMPTY);
|
||||
snapshot.getSpec().setRawPatch(StringUtils.EMPTY);
|
||||
})
|
||||
.map(snapshot -> {
|
||||
Set<String> usernames = snapshot.getSpec().getContributors();
|
||||
return Objects.requireNonNullElseGet(usernames,
|
||||
|
@ -367,7 +370,6 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
// update in progress status
|
||||
String releaseSnapshot = singlePage.getSpec().getReleaseSnapshot();
|
||||
status.setInProgress(!StringUtils.equals(releaseSnapshot, headSnapshot));
|
||||
});
|
||||
|
||||
if (singlePage.isPublished() && status.getLastModifyTime() == null) {
|
||||
client.fetch(Snapshot.class, singlePage.getSpec().getReleaseSnapshot())
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.Objects;
|
|||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
@ -20,7 +21,7 @@ import org.springframework.util.comparator.Comparators;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.PostService;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
|
@ -47,6 +48,7 @@ import run.halo.app.theme.finders.vo.StatsVo;
|
|||
* @since 2.0.0
|
||||
*/
|
||||
@Finder("postFinder")
|
||||
@AllArgsConstructor
|
||||
public class PostFinderImpl implements PostFinder {
|
||||
|
||||
public static final Predicate<Post> FIXED_PREDICATE = post -> post.isPublished()
|
||||
|
@ -54,7 +56,7 @@ public class PostFinderImpl implements PostFinder {
|
|||
&& Post.VisibleEnum.PUBLIC.equals(post.getSpec().getVisible());
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
private final ContentService contentService;
|
||||
private final PostService postService;
|
||||
|
||||
private final TagFinder tagFinder;
|
||||
|
||||
|
@ -64,19 +66,6 @@ public class PostFinderImpl implements PostFinder {
|
|||
|
||||
private final CounterService counterService;
|
||||
|
||||
public PostFinderImpl(ReactiveExtensionClient client,
|
||||
ContentService contentService,
|
||||
TagFinder tagFinder,
|
||||
CategoryFinder categoryFinder,
|
||||
ContributorFinder contributorFinder, CounterService counterService) {
|
||||
this.client = client;
|
||||
this.contentService = contentService;
|
||||
this.tagFinder = tagFinder;
|
||||
this.categoryFinder = categoryFinder;
|
||||
this.contributorFinder = contributorFinder;
|
||||
this.counterService = counterService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<PostVo> getByName(String postName) {
|
||||
return client.fetch(Post.class, postName)
|
||||
|
@ -90,9 +79,7 @@ public class PostFinderImpl implements PostFinder {
|
|||
|
||||
@Override
|
||||
public Mono<ContentVo> content(String postName) {
|
||||
return client.fetch(Post.class, postName)
|
||||
.map(post -> post.getSpec().getReleaseSnapshot())
|
||||
.flatMap(contentService::getContent)
|
||||
return postService.getReleaseContent(postName)
|
||||
.map(wrapper -> ContentVo.builder().content(wrapper.getContent())
|
||||
.raw(wrapper.getRaw()).build());
|
||||
}
|
||||
|
|
|
@ -6,11 +6,12 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.SinglePageService;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.core.extension.content.SinglePage;
|
||||
import run.halo.app.extension.ListResult;
|
||||
|
@ -32,6 +33,7 @@ import run.halo.app.theme.finders.vo.StatsVo;
|
|||
* @since 2.0.0
|
||||
*/
|
||||
@Finder("singlePageFinder")
|
||||
@AllArgsConstructor
|
||||
public class SinglePageFinderImpl implements SinglePageFinder {
|
||||
|
||||
public static final Predicate<SinglePage> FIXED_PREDICATE = page -> page.isPublished()
|
||||
|
@ -40,20 +42,12 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
|||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
private final ContentService contentService;
|
||||
private final SinglePageService singlePageService;
|
||||
|
||||
private final ContributorFinder contributorFinder;
|
||||
|
||||
private final CounterService counterService;
|
||||
|
||||
public SinglePageFinderImpl(ReactiveExtensionClient client, ContentService contentService,
|
||||
ContributorFinder contributorFinder, CounterService counterService) {
|
||||
this.client = client;
|
||||
this.contentService = contentService;
|
||||
this.contributorFinder = contributorFinder;
|
||||
this.counterService = counterService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SinglePageVo> getByName(String pageName) {
|
||||
return client.fetch(SinglePage.class, pageName)
|
||||
|
@ -80,9 +74,7 @@ public class SinglePageFinderImpl implements SinglePageFinder {
|
|||
|
||||
@Override
|
||||
public Mono<ContentVo> content(String pageName) {
|
||||
return client.fetch(SinglePage.class, pageName)
|
||||
.map(page -> page.getSpec().getReleaseSnapshot())
|
||||
.flatMap(contentService::getContent)
|
||||
return singlePageService.getReleaseContent(pageName)
|
||||
.map(wrapper -> ContentVo.builder().content(wrapper.getContent())
|
||||
.raw(wrapper.getRaw()).build());
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ rules:
|
|||
resources: [ "posts" ]
|
||||
verbs: [ "*" ]
|
||||
- apiGroups: [ "api.console.halo.run" ]
|
||||
resources: [ "posts", "posts/publish", "posts/unpublish", "posts/recycle", "posts/content", "contents", "contents/publish" ]
|
||||
resources: [ "posts", "posts/publish", "posts/unpublish", "posts/recycle", "posts/content" ]
|
||||
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
|
@ -37,5 +37,5 @@ rules:
|
|||
resources: [ "posts" ]
|
||||
verbs: [ "get", "list" ]
|
||||
- apiGroups: [ "api.console.halo.run" ]
|
||||
resources: [ "posts", "contents" ]
|
||||
resources: [ "posts", "posts/head-content", "posts/release-content" ]
|
||||
verbs: [ "get", "list" ]
|
||||
|
|
|
@ -15,7 +15,7 @@ rules:
|
|||
resources: [ "singlepages" ]
|
||||
verbs: [ "*" ]
|
||||
- apiGroups: [ "api.console.halo.run" ]
|
||||
resources: [ "singlepages", "singlepages/publish", "singlepages/content", "contents", "contents/publish" ]
|
||||
resources: [ "singlepages", "singlepages/publish", "singlepages/content" ]
|
||||
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
|
@ -35,5 +35,5 @@ rules:
|
|||
resources: [ "singlepages" ]
|
||||
verbs: [ "get", "list" ]
|
||||
- apiGroups: [ "api.console.halo.run" ]
|
||||
resources: [ "singlepages", "contents" ]
|
||||
resources: [ "singlepages", "singlepages/head-content", "singlepages/release-content" ]
|
||||
verbs: [ "get", "list" ]
|
||||
|
|
|
@ -1,240 +0,0 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
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 static run.halo.app.content.TestPost.snapshotV1;
|
||||
import static run.halo.app.content.TestPost.snapshotV2;
|
||||
import static run.halo.app.content.TestPost.snapshotV3;
|
||||
|
||||
import java.util.HashMap;
|
||||
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.Mock;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.content.impl.ContentServiceImpl;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.core.extension.content.Snapshot;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* Tests for {@link ContentService}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@WithMockUser(username = "guqing")
|
||||
@ExtendWith(SpringExtension.class)
|
||||
class ContentServiceTest {
|
||||
|
||||
@Mock
|
||||
private ReactiveExtensionClient client;
|
||||
|
||||
private ContentService contentService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
contentService = new ContentServiceImpl(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
void draftContent() {
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
Ref ref = postRef("test-post");
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
|
||||
ContentRequest contentRequest =
|
||||
new ContentRequest(ref, null,
|
||||
snapshotV1.getSpec().getRawPatch(),
|
||||
snapshotV1.getSpec().getContentPatch(),
|
||||
snapshotV1.getSpec().getRawType());
|
||||
|
||||
pilingBaseSnapshot(snapshotV1);
|
||||
ContentWrapper contentWrapper = ContentWrapper.builder()
|
||||
.snapshotName("snapshot-A")
|
||||
.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));
|
||||
|
||||
StepVerifier.create(contentService.draftContent(contentRequest))
|
||||
.expectNext(contentWrapper)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
verify(client, times(1)).create(captor.capture());
|
||||
Snapshot snapshot = captor.getValue();
|
||||
|
||||
assertThat(snapshot.getMetadata().getName())
|
||||
.isNotEqualTo(snapshotV1.getMetadata().getName());
|
||||
assertThat(snapshot.getSpec().getLastModifyTime()).isNotNull();
|
||||
assertThat(snapshot.getSpec().getOwner()).isEqualTo("guqing");
|
||||
assertThat(snapshot.getSpec().getContributors()).isEqualTo(Set.of("guqing"));
|
||||
assertThat(snapshot.getSpec().getSubjectRef()).isEqualTo(ref);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateContent() {
|
||||
String headSnapshot = "snapshot-A";
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
Ref ref = postRef("test-post");
|
||||
|
||||
Snapshot updated = snapshotV1();
|
||||
updated.getSpec().setRawPatch("hello");
|
||||
updated.getSpec().setContentPatch("<p>hello</p>");
|
||||
updated.getSpec().setSubjectRef(ref);
|
||||
ContentRequest contentRequest =
|
||||
new ContentRequest(ref, headSnapshot,
|
||||
snapshotV1.getSpec().getRawPatch(),
|
||||
snapshotV1.getSpec().getContentPatch(),
|
||||
snapshotV1.getSpec().getRawType());
|
||||
|
||||
pilingBaseSnapshot(snapshotV1);
|
||||
|
||||
when(client.fetch(eq(Snapshot.class), eq(contentRequest.headSnapshotName())))
|
||||
.thenReturn(Mono.just(updated));
|
||||
|
||||
ContentWrapper contentWrapper = ContentWrapper.builder()
|
||||
.snapshotName(headSnapshot)
|
||||
.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));
|
||||
|
||||
StepVerifier.create(contentService.updateContent(contentRequest))
|
||||
.expectNext(contentWrapper)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
verify(client, times(1)).update(captor.capture());
|
||||
Snapshot snapshot = captor.getValue();
|
||||
|
||||
assertThat(snapshot).isEqualTo(updated);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateContentWhenHeadPoints2Published() {
|
||||
final Ref ref = postRef("test-post");
|
||||
// v1(released),v2
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
ExtensionUtil.nullSafeAnnotations(snapshotV1)
|
||||
.put(Snapshot.KEEP_RAW_ANNO, "true");
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
|
||||
Snapshot snapshotV2 = snapshotV2();
|
||||
snapshotV2.getSpec().setSubjectRef(ref);
|
||||
|
||||
final String headSnapshot = snapshotV2.getMetadata().getName();
|
||||
|
||||
pilingBaseSnapshot(snapshotV2, snapshotV1);
|
||||
|
||||
when(client.fetch(eq(Snapshot.class), eq(snapshotV2.getMetadata().getName())))
|
||||
.thenReturn(Mono.just(snapshotV2));
|
||||
when(client.fetch(eq(Snapshot.class), eq(snapshotV1.getMetadata().getName())))
|
||||
.thenReturn(Mono.just(snapshotV1));
|
||||
|
||||
ContentRequest contentRequest =
|
||||
new ContentRequest(ref, headSnapshot, "C",
|
||||
"<p>C</p>", snapshotV1.getSpec().getRawType());
|
||||
|
||||
when(client.update(any())).thenReturn(Mono.just(snapshotV2));
|
||||
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(ref))
|
||||
.expectNext(snapshotV2)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
StepVerifier.create(contentService.updateContent(contentRequest))
|
||||
.consumeNextWith(updated -> {
|
||||
assertThat(updated.getRaw()).isEqualTo("C");
|
||||
assertThat(updated.getContent()).isEqualTo("<p>C</p>");
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void pilingBaseSnapshot(Snapshot... expected) {
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void baseSnapshotVersion() {
|
||||
String postName = "post-1";
|
||||
final Ref ref = postRef(postName);
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.getSpec().setSubjectRef(ref);
|
||||
|
||||
Snapshot snapshotV3 = TestPost.snapshotV3();
|
||||
snapshotV3.getSpec().setSubjectRef(ref);
|
||||
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV2, snapshotV1, snapshotV3));
|
||||
|
||||
StepVerifier.create(contentService.getBaseSnapshot(ref))
|
||||
.expectNext(snapshotV1)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void latestSnapshotVersion() {
|
||||
String postName = "post-1";
|
||||
final Ref ref = postRef(postName);
|
||||
Snapshot snapshotV1 = snapshotV1();
|
||||
snapshotV1.getMetadata().setLabels(new HashMap<>());
|
||||
snapshotV1.getSpec().setSubjectRef(ref);
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.getSpec().setSubjectRef(ref);
|
||||
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2));
|
||||
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(ref))
|
||||
.expectNext(snapshotV2)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
Snapshot snapshotV3 = snapshotV3();
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2, snapshotV3));
|
||||
StepVerifier.create(contentService.latestSnapshotVersion(ref))
|
||||
.expectNext(snapshotV3)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
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.when;
|
||||
|
||||
import java.time.Instant;
|
||||
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 reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.content.TestPost;
|
||||
import run.halo.app.core.extension.content.Snapshot;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
||||
/**
|
||||
* Tests for {@link ContentServiceImpl}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ContentServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private ReactiveExtensionClient client;
|
||||
|
||||
@InjectMocks
|
||||
private ContentServiceImpl contentService;
|
||||
|
||||
@Test
|
||||
void getBaseSnapshot() {
|
||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
ExtensionUtil.nullSafeAnnotations(snapshotV1)
|
||||
.put(Snapshot.KEEP_RAW_ANNO, "true");
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(TestPost.snapshotV2(), snapshotV1, TestPost.snapshotV3()));
|
||||
contentService.getBaseSnapshot(Ref.of("fake-post"))
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(
|
||||
baseSnapshot -> assertThat(baseSnapshot.getMetadata().getName())
|
||||
.isEqualTo(snapshotV1.getMetadata().getName()))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void latestSnapshotVersion() {
|
||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
snapshotV1.getMetadata().setCreationTimestamp(Instant.now());
|
||||
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV2.getMetadata().setCreationTimestamp(Instant.now().plusSeconds(2));
|
||||
|
||||
Snapshot snapshotV3 = TestPost.snapshotV3();
|
||||
snapshotV3.getMetadata().setCreationTimestamp(Instant.now().plusSeconds(3));
|
||||
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(Flux.just(snapshotV2, snapshotV1, snapshotV3));
|
||||
|
||||
contentService.latestSnapshotVersion(Ref.of("fake-post"))
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(s -> {
|
||||
assertThat(s.getMetadata().getName()).isEqualTo(snapshotV3.getMetadata().getName());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import org.mockito.Mock;
|
|||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.PostQuery;
|
||||
import run.halo.app.content.TestPost;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
|
@ -29,9 +28,6 @@ class PostServiceImplTest {
|
|||
@Mock
|
||||
private ReactiveExtensionClient client;
|
||||
|
||||
@Mock
|
||||
private ContentService contentService;
|
||||
|
||||
@InjectMocks
|
||||
private PostServiceImpl postService;
|
||||
|
||||
|
|
|
@ -20,9 +20,7 @@ import org.mockito.InjectMocks;
|
|||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.PostService;
|
||||
import run.halo.app.content.TestPost;
|
||||
|
@ -44,8 +42,7 @@ class PostReconcilerTest {
|
|||
|
||||
@Mock
|
||||
private ExtensionClient client;
|
||||
@Mock
|
||||
private ContentService contentService;
|
||||
|
||||
@Mock
|
||||
private PostPermalinkPolicy postPermalinkPolicy;
|
||||
|
||||
|
@ -66,15 +63,16 @@ class PostReconcilerTest {
|
|||
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())))
|
||||
when(postService.getContent(eq(post.getSpec().getReleaseSnapshot()),
|
||||
eq(post.getSpec().getBaseSnapshot())))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
||||
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2));
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(List.of(snapshotV1, snapshotV2));
|
||||
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
postReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
@ -101,7 +99,8 @@ class PostReconcilerTest {
|
|||
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())))
|
||||
when(postService.getContent(eq(post.getSpec().getReleaseSnapshot()),
|
||||
eq(post.getSpec().getBaseSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(post.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
|
@ -116,8 +115,8 @@ class PostReconcilerTest {
|
|||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2));
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(List.of(snapshotV1, snapshotV2));
|
||||
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
postReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
@ -138,7 +137,8 @@ class PostReconcilerTest {
|
|||
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())))
|
||||
when(postService.getContent(eq(post.getSpec().getReleaseSnapshot()),
|
||||
eq(post.getSpec().getBaseSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(post.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
|
@ -151,8 +151,8 @@ class PostReconcilerTest {
|
|||
when(client.fetch(eq(Snapshot.class), eq(post.getSpec().getReleaseSnapshot())))
|
||||
.thenReturn(Optional.of(snapshotV2));
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.empty());
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(List.of());
|
||||
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
postReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
@ -171,7 +171,8 @@ class PostReconcilerTest {
|
|||
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())))
|
||||
when(postService.getContent(eq(post.getSpec().getReleaseSnapshot()),
|
||||
eq(post.getSpec().getBaseSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(post.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
|
@ -179,8 +180,8 @@ class PostReconcilerTest {
|
|||
.rawType("markdown")
|
||||
.build()));
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.empty());
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(List.of());
|
||||
|
||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||
postReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
|
|
@ -22,9 +22,7 @@ import org.mockito.InjectMocks;
|
|||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.ContentWrapper;
|
||||
import run.halo.app.content.SinglePageService;
|
||||
import run.halo.app.content.TestPost;
|
||||
|
@ -50,8 +48,6 @@ import run.halo.app.theme.router.PermalinkIndexUpdateCommand;
|
|||
class SinglePageReconcilerTest {
|
||||
@Mock
|
||||
private ExtensionClient client;
|
||||
@Mock
|
||||
private ContentService contentService;
|
||||
|
||||
@Mock
|
||||
private ApplicationContext applicationContext;
|
||||
|
@ -75,7 +71,8 @@ class SinglePageReconcilerTest {
|
|||
page.getSpec().setHeadSnapshot("page-A-head-snapshot");
|
||||
when(client.fetch(eq(SinglePage.class), eq(name)))
|
||||
.thenReturn(Optional.of(page));
|
||||
when(contentService.getContent(eq(page.getSpec().getHeadSnapshot())))
|
||||
when(singlePageService.getContent(eq(page.getSpec().getHeadSnapshot()),
|
||||
eq(page.getSpec().getBaseSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(page.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
|
@ -88,8 +85,8 @@ class SinglePageReconcilerTest {
|
|||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
||||
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.just(snapshotV1, snapshotV2));
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(List.of(snapshotV1, snapshotV2));
|
||||
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
|
||||
|
||||
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
||||
|
@ -138,7 +135,8 @@ class SinglePageReconcilerTest {
|
|||
page.getSpec().setReleaseSnapshot("page-fake-released-snapshot");
|
||||
when(client.fetch(eq(SinglePage.class), eq(name)))
|
||||
.thenReturn(Optional.of(page));
|
||||
when(contentService.getContent(eq(page.getSpec().getHeadSnapshot())))
|
||||
when(singlePageService.getContent(eq(page.getSpec().getHeadSnapshot()),
|
||||
eq(page.getSpec().getBaseSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(page.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
|
@ -152,8 +150,8 @@ class SinglePageReconcilerTest {
|
|||
when(client.fetch(eq(Snapshot.class), eq(page.getSpec().getReleaseSnapshot())))
|
||||
.thenReturn(Optional.of(snapshotV2));
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.empty());
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(List.of());
|
||||
|
||||
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
||||
singlePageReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
@ -172,7 +170,8 @@ class SinglePageReconcilerTest {
|
|||
page.getSpec().setPublish(false);
|
||||
when(client.fetch(eq(SinglePage.class), eq(name)))
|
||||
.thenReturn(Optional.of(page));
|
||||
when(contentService.getContent(eq(page.getSpec().getHeadSnapshot())))
|
||||
when(singlePageService.getContent(eq(page.getSpec().getHeadSnapshot()),
|
||||
eq(page.getSpec().getBaseSnapshot())))
|
||||
.thenReturn(Mono.just(ContentWrapper.builder()
|
||||
.snapshotName(page.getSpec().getHeadSnapshot())
|
||||
.raw("hello world")
|
||||
|
@ -181,8 +180,8 @@ class SinglePageReconcilerTest {
|
|||
.build())
|
||||
);
|
||||
|
||||
when(contentService.listSnapshots(any()))
|
||||
.thenReturn(Flux.empty());
|
||||
when(client.list(eq(Snapshot.class), any(), any()))
|
||||
.thenReturn(List.of());
|
||||
|
||||
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
||||
singlePageReconciler.reconcile(new Reconciler.Request(name));
|
||||
|
|
|
@ -20,8 +20,8 @@ import org.mockito.Mock;
|
|||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
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.core.extension.content.Post;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.Metadata;
|
||||
|
@ -47,10 +47,10 @@ class PostFinderImplTest {
|
|||
private ReactiveExtensionClient client;
|
||||
|
||||
@Mock
|
||||
private ContentService contentService;
|
||||
private CounterService counterService;
|
||||
|
||||
@Mock
|
||||
private CounterService counterService;
|
||||
private PostService postService;
|
||||
|
||||
@Mock
|
||||
private CategoryFinder categoryFinder;
|
||||
|
@ -74,9 +74,7 @@ class PostFinderImplTest {
|
|||
.content("content")
|
||||
.rawType("rawType")
|
||||
.build();
|
||||
when(client.fetch(eq(Post.class), eq("post-1")))
|
||||
.thenReturn(Mono.just(post));
|
||||
when(contentService.getContent(post.getSpec().getReleaseSnapshot()))
|
||||
when(postService.getReleaseContent(eq(post.getMetadata().getName())))
|
||||
.thenReturn(Mono.just(contentWrapper));
|
||||
ContentVo content = postFinder.content("post-1").block();
|
||||
assertThat(content.getContent()).isEqualTo(contentWrapper.getContent());
|
||||
|
|
|
@ -14,7 +14,7 @@ import org.mockito.Mock;
|
|||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.content.ContentService;
|
||||
import run.halo.app.content.SinglePageService;
|
||||
import run.halo.app.core.extension.content.SinglePage;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
|
@ -34,7 +34,7 @@ class SinglePageFinderImplTest {
|
|||
private ReactiveExtensionClient client;
|
||||
|
||||
@Mock
|
||||
private ContentService contentService;
|
||||
private SinglePageService singlePageService;
|
||||
|
||||
@Mock
|
||||
private ContributorFinder contributorFinder;
|
||||
|
@ -61,7 +61,7 @@ class SinglePageFinderImplTest {
|
|||
|
||||
when(counterService.getByName(anyString())).thenReturn(Mono.empty());
|
||||
when(contributorFinder.getContributor(anyString())).thenReturn(Mono.empty());
|
||||
when(contentService.getContent(anyString())).thenReturn(Mono.empty());
|
||||
when(singlePageService.getReleaseContent(anyString())).thenReturn(Mono.empty());
|
||||
|
||||
singlePageFinder.getByName(fakePageName)
|
||||
.as(StepVerifier::create)
|
||||
|
@ -71,9 +71,9 @@ class SinglePageFinderImplTest {
|
|||
})
|
||||
.verifyComplete();
|
||||
|
||||
verify(client, times(2)).fetch(SinglePage.class, fakePageName);
|
||||
verify(client, times(1)).fetch(SinglePage.class, fakePageName);
|
||||
verify(counterService).getByName(anyString());
|
||||
verify(contentService).getContent(anyString());
|
||||
verify(singlePageService).getReleaseContent(anyString());
|
||||
verify(contributorFinder).getContributor(anyString());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue