Skip to content

Commit

Permalink
feat: add features for view post history snapshots (#5787)
Browse files Browse the repository at this point in the history
* Add snapshots related api

* feat: add features for view post history snapshots

---------

Co-authored-by: guqing <i@guqing.email>
  • Loading branch information
ruibaby and guqing committed Apr 26, 2024
1 parent 1ade849 commit 58f82d2
Show file tree
Hide file tree
Showing 45 changed files with 2,830 additions and 45 deletions.
374 changes: 372 additions & 2 deletions api-docs/openapi/v3_0/aggregated.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package run.halo.app.content;

import static run.halo.app.extension.index.query.QueryFactory.and;
import static run.halo.app.extension.index.query.QueryFactory.equal;
import static run.halo.app.extension.index.query.QueryFactory.isNull;

import java.security.Principal;
import java.time.Duration;
import java.time.Instant;
Expand All @@ -8,15 +12,20 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Sort;
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.Flux;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import run.halo.app.core.extension.content.Snapshot;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.MetadataUtil;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.Ref;
import run.halo.app.extension.router.selector.FieldSelector;

/**
* Abstract Service for {@link Snapshot}.
Expand Down Expand Up @@ -62,34 +71,41 @@ protected void checkBaseSnapshot(Snapshot snapshot) {
}

protected Mono<ContentWrapper> draftContent(@Nullable String baseSnapshotName,
ContentRequest contentRequest,
@Nullable String parentSnapshotName) {
return create(baseSnapshotName, contentRequest, parentSnapshotName)
.flatMap(head -> {
String baseSnapshotNameToUse =
StringUtils.defaultIfBlank(baseSnapshotName, head.getMetadata().getName());
return restoredContent(baseSnapshotNameToUse, head);
});
}

protected Mono<ContentWrapper> draftContent(String baseSnapshotName, ContentRequest content) {
return this.draftContent(baseSnapshotName, content, content.headSnapshotName());
}

private Mono<Snapshot> create(@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.defaultIfBlank(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 -> {
.doOnNext(username -> {
Snapshot.addContributor(source, username);
source.getSpec().setOwner(username);
return source;
})
.defaultIfEmpty(source)
.thenReturn(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());
.flatMap(client::create);
}

protected Mono<ContentWrapper> updateContent(String baseSnapshotName,
Expand All @@ -98,17 +114,23 @@ protected Mono<ContentWrapper> updateContent(String baseSnapshotName,
Assert.notNull(baseSnapshotName, "The baseSnapshotName must not be null");
Assert.notNull(contentRequest.headSnapshotName(), "The headSnapshotName must not be null");
return Mono.defer(() -> client.fetch(Snapshot.class, contentRequest.headSnapshotName())
.flatMap(headSnapshot -> {
var oldVersion = contentRequest.version();
var version = headSnapshot.getMetadata().getVersion();
if (hasConflict(oldVersion, version)) {
// draft a new snapshot as the head snapshot
return create(baseSnapshotName, contentRequest,
contentRequest.headSnapshotName());
}
return Mono.just(headSnapshot);
})
.flatMap(headSnapshot -> client.fetch(Snapshot.class, baseSnapshotName)
.map(baseSnapshot -> determineRawAndContentPatch(headSnapshot, baseSnapshot,
contentRequest)
)
.map(baseSnapshot -> determineRawAndContentPatch(headSnapshot,
baseSnapshot, contentRequest))
)
.flatMap(headSnapshot -> getContextUsername()
.map(username -> {
Snapshot.addContributor(headSnapshot, username);
return headSnapshot;
})
.defaultIfEmpty(headSnapshot)
.doOnNext(username -> Snapshot.addContributor(headSnapshot, username))
.thenReturn(headSnapshot)
)
.flatMap(client::update)
)
Expand All @@ -117,6 +139,19 @@ protected Mono<ContentWrapper> updateContent(String baseSnapshotName,
.flatMap(head -> restoredContent(baseSnapshotName, head));
}

protected Flux<Snapshot> listSnapshotsBy(Ref ref) {
var snapshotListOptions = new ListOptions();
var query = and(isNull("metadata.deletionTimestamp"),
equal("spec.subjectRef", Snapshot.toSubjectRefKey(ref)));
snapshotListOptions.setFieldSelector(FieldSelector.of(query));
var sort = Sort.by("metadata.creationTimestamp", "metadata.name").descending();
return client.listAll(Snapshot.class, snapshotListOptions, sort);
}

boolean hasConflict(Long oldVersion, Long newVersion) {
return oldVersion != null && !newVersion.equals(oldVersion);
}

protected Mono<ContentWrapper> restoredContent(String baseSnapshotName, Snapshot headSnapshot) {
return client.fetch(Snapshot.class, baseSnapshotName)
.doOnNext(this::checkBaseSnapshot)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package run.halo.app.content;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap;
import lombok.Builder;
import org.apache.commons.lang3.StringUtils;
import run.halo.app.core.extension.content.Snapshot;
import run.halo.app.extension.Metadata;
Expand All @@ -13,8 +15,10 @@
* @author guqing
* @since 2.0.0
*/
@Builder
public record ContentRequest(@Schema(requiredMode = REQUIRED) Ref subjectRef,
String headSnapshotName,
@Schema(requiredMode = NOT_REQUIRED) Long version,
@Schema(requiredMode = REQUIRED) String raw,
@Schema(requiredMode = REQUIRED) String content,
@Schema(requiredMode = REQUIRED) String rawType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package run.halo.app.content;

public record ContentUpdateParam(Long version, String raw, String content, String rawType) {

public static ContentUpdateParam from(Content content) {
return new ContentUpdateParam(null, content.raw(), content.content(),
content.rawType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package run.halo.app.content;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.Instant;
import lombok.Data;
import lombok.experimental.Accessors;
import run.halo.app.core.extension.content.Snapshot;
import run.halo.app.extension.MetadataOperator;

@Data
@Accessors(chain = true)
public class ListedSnapshotDto {
@Schema(requiredMode = REQUIRED)
private MetadataOperator metadata;

@Schema(requiredMode = REQUIRED)
private Spec spec;

@Data
@Accessors(chain = true)
@Schema(name = "ListedSnapshotSpec")
public static class Spec {
@Schema(requiredMode = REQUIRED)
private String owner;

private Instant modifyTime;
}

/**
* Creates from snapshot.
*/
public static ListedSnapshotDto from(Snapshot snapshot) {
return new ListedSnapshotDto()
.setMetadata(snapshot.getMetadata())
.setSpec(new Spec()
.setOwner(snapshot.getSpec().getOwner())
.setModifyTime(snapshot.getSpec().getLastModifyTime())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
* @since 2.0.0
*/
public record PostRequest(@Schema(requiredMode = REQUIRED) @NonNull Post post,
Content content) {
ContentUpdateParam content) {

public ContentRequest contentRequest() {
Ref subjectRef = Ref.of(post);
return new ContentRequest(subjectRef, post.getSpec().getHeadSnapshot(), content.raw(),
content.content(), content.rawType());
return new ContentRequest(subjectRef, post.getSpec().getHeadSnapshot(), content.version(),
content.raw(), content.content(), content.rawType());
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package run.halo.app.content;

import org.springframework.lang.NonNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.content.Post;
import run.halo.app.extension.ListResult;
Expand Down Expand Up @@ -31,6 +32,8 @@ public interface PostService {

Mono<ContentWrapper> getContent(String snapshotName, String baseSnapshotName);

Flux<ListedSnapshotDto> listSnapshots(String name);

Mono<Post> publish(Post post);

Mono<Post> unpublish(Post post);
Expand All @@ -43,4 +46,8 @@ public interface PostService {
* @return full post data or empty.
*/
Mono<Post> getByUsername(String postName, String username);

Mono<Post> revertToSpecifiedSnapshot(String postName, String snapshotName);

Mono<ContentWrapper> deleteContent(String postName, String snapshotName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
* @since 2.0.0
*/
public record SinglePageRequest(@Schema(requiredMode = REQUIRED) SinglePage page,
@Schema(requiredMode = REQUIRED) Content content) {
@Schema(requiredMode = REQUIRED) ContentUpdateParam content) {

public ContentRequest contentRequest() {
Ref subjectRef = Ref.of(page);
return new ContentRequest(subjectRef, page.getSpec().getHeadSnapshot(), content.raw(),
content.content(), content.rawType());
return new ContentRequest(subjectRef, page.getSpec().getHeadSnapshot(), content.version(),
content.raw(), content.content(), content.rawType());
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package run.halo.app.content;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.content.SinglePage;
import run.halo.app.extension.ListResult;
Expand All @@ -18,9 +19,15 @@ public interface SinglePageService {

Mono<ContentWrapper> getContent(String snapshotName, String baseSnapshotName);

Flux<ListedSnapshotDto> listSnapshots(String pageName);

Mono<ListResult<ListedSinglePage>> list(SinglePageQuery listRequest);

Mono<SinglePage> draft(SinglePageRequest pageRequest);

Mono<SinglePage> update(SinglePageRequest pageRequest);

Mono<SinglePage> revertToSpecifiedSnapshot(String pageName, String snapshotName);

Mono<ContentWrapper> deleteContent(String postName, String snapshotName);
}
Loading

0 comments on commit 58f82d2

Please sign in to comment.