mirror of https://github.com/halo-dev/halo
refactor: using indexes to query post lists (#5230)
#### What type of PR is this? /kind feature /area core /area console /milestone 2.12.x #### What this PR does / why we need it: 使用索引功能来查询文章列表 how to test it? 1. 测试文章列表的筛选条件是否正确 2. 测试文章列表中关联的标签和分类信息是否正确 3. 测试仪表盘的文章数量统计是否正确 4. 测试分类关联文章的数量是否正确 5. 测试标签关联文章的文章是否正确 6. 测试主题端文章列表是否正确 #### Which issue(s) this PR fixes: Fixes #5223 #### Does this PR introduce a user-facing change? ```release-note 使用高级索引功能检索文章以显著降低资源消耗并提供更快、更高效的文章检索体验 ```pull/5250/head v2.12.0-alpha.2
parent
cecdb3f9ef
commit
3f27f6f262
|
@ -84,4 +84,7 @@ public class Snapshot extends AbstractExtension {
|
||||||
return Boolean.parseBoolean(annotations.get(Snapshot.KEEP_RAW_ANNO));
|
return Boolean.parseBoolean(annotations.get(Snapshot.KEEP_RAW_ANNO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String toSubjectRefKey(Ref subjectRef) {
|
||||||
|
return subjectRef.getGroup() + "/" + subjectRef.getKind() + "/" + subjectRef.getName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -144,6 +145,15 @@ public class ListResult<T> implements Iterable<T>, Supplier<Stream<T>> {
|
||||||
return listSort;
|
return listSort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first element of the list result.
|
||||||
|
*/
|
||||||
|
public static <T> Optional<T> first(ListResult<T> listResult) {
|
||||||
|
return Optional.ofNullable(listResult)
|
||||||
|
.map(ListResult::getItems)
|
||||||
|
.map(list -> list.isEmpty() ? null : list.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<T> get() {
|
public Stream<T> get() {
|
||||||
return items.stream();
|
return items.stream();
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package run.halo.app.extension.router.selector;
|
package run.halo.app.extension.router.selector;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import run.halo.app.extension.index.query.Query;
|
import run.halo.app.extension.index.query.Query;
|
||||||
import run.halo.app.extension.index.query.QueryFactory;
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
|
|
||||||
public record FieldSelector(Query query) {
|
public record FieldSelector(@NonNull Query query) {
|
||||||
public FieldSelector(Query query) {
|
public FieldSelector(Query query) {
|
||||||
this.query = Objects.requireNonNullElseGet(query, QueryFactory::all);
|
this.query = Objects.requireNonNullElseGet(query, QueryFactory::all);
|
||||||
}
|
}
|
||||||
|
@ -12,4 +14,13 @@ public record FieldSelector(Query query) {
|
||||||
public static FieldSelector of(Query query) {
|
public static FieldSelector of(Query query) {
|
||||||
return new FieldSelector(query);
|
return new FieldSelector(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FieldSelector all() {
|
||||||
|
return new FieldSelector(QueryFactory.all());
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldSelector andQuery(Query other) {
|
||||||
|
Assert.notNull(other, "Query must not be null");
|
||||||
|
return of(QueryFactory.and(query(), other));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,22 @@ public class LabelSelector implements Predicate<Map<String, String>> {
|
||||||
.allMatch(matcher -> matcher.test(labels.get(matcher.getKey())));
|
.allMatch(matcher -> matcher.test(labels.get(matcher.getKey())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new label selector that is the result of ANDing the current selector with the
|
||||||
|
* given selector.
|
||||||
|
*
|
||||||
|
* @param other the selector to AND with
|
||||||
|
* @return a new label selector
|
||||||
|
*/
|
||||||
|
public LabelSelector and(LabelSelector other) {
|
||||||
|
var labelSelector = new LabelSelector();
|
||||||
|
var matchers = new ArrayList<SelectorMatcher>();
|
||||||
|
matchers.addAll(this.matchers);
|
||||||
|
matchers.addAll(other.matchers);
|
||||||
|
labelSelector.setMatchers(matchers);
|
||||||
|
return labelSelector;
|
||||||
|
}
|
||||||
|
|
||||||
public static LabelSelectorBuilder builder() {
|
public static LabelSelectorBuilder builder() {
|
||||||
return new LabelSelectorBuilder();
|
return new LabelSelectorBuilder();
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,8 @@ public final class SelectorUtil {
|
||||||
listOptions.setLabelSelector(new LabelSelector().setMatchers(labelMatchers));
|
listOptions.setLabelSelector(new LabelSelector().setMatchers(labelMatchers));
|
||||||
if (!fieldQuery.isEmpty()) {
|
if (!fieldQuery.isEmpty()) {
|
||||||
listOptions.setFieldSelector(FieldSelector.of(QueryFactory.and(fieldQuery)));
|
listOptions.setFieldSelector(FieldSelector.of(QueryFactory.and(fieldQuery)));
|
||||||
|
} else {
|
||||||
|
listOptions.setFieldSelector(FieldSelector.all());
|
||||||
}
|
}
|
||||||
return listOptions;
|
return listOptions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
package run.halo.app.content;
|
|
||||||
|
|
||||||
import com.google.common.collect.HashMultimap;
|
|
||||||
import com.google.common.collect.SetMultimap;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import run.halo.app.extension.Extension;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>A default implementation of {@link Indexer}.</p>
|
|
||||||
* <p>Note that this Indexer is not thread-safe, If multiple threads access this indexer
|
|
||||||
* concurrently and one of the threads modifies the indexer, it must be synchronized externally.</p>
|
|
||||||
*
|
|
||||||
* @param <T> the type of object to be indexed
|
|
||||||
* @author guqing
|
|
||||||
* @see
|
|
||||||
* <a href="https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/client-go/tools/cache/index.go">kubernetes index</a>
|
|
||||||
* @see <a href="https://juejin.cn/post/7132767272841510926">informer机制之cache.indexer机制</a>
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
public class DefaultIndexer<T extends Extension> implements Indexer<T> {
|
|
||||||
private final Map<String, SetMultimap<String, String>> indices = new HashMap<>();
|
|
||||||
private final Map<String, IndexFunc<T>> indexFuncMap = new HashMap<>();
|
|
||||||
private final Map<String, SetMultimap<String, String>> indexValues = new HashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addIndexFunc(String indexName, IndexFunc<T> indexFunc) {
|
|
||||||
indexFuncMap.put(indexName, indexFunc);
|
|
||||||
indices.put(indexName, HashMultimap.create());
|
|
||||||
indexValues.put(indexName, HashMultimap.create());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> indexNames() {
|
|
||||||
return Set.copyOf(indexFuncMap.keySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(String indexName, T obj) {
|
|
||||||
IndexFunc<T> indexFunc = getIndexFunc(indexName);
|
|
||||||
Set<String> indexKeys = indexFunc.apply(obj);
|
|
||||||
for (String indexKey : indexKeys) {
|
|
||||||
SetMultimap<String, String> index = indices.get(indexName);
|
|
||||||
index.put(indexKey, getObjectKey(obj));
|
|
||||||
|
|
||||||
SetMultimap<String, String> indexValue = indexValues.get(indexName);
|
|
||||||
indexValue.put(getObjectKey(obj), indexKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private IndexFunc<T> getIndexFunc(String indexName) {
|
|
||||||
IndexFunc<T> indexFunc = indexFuncMap.get(indexName);
|
|
||||||
if (indexFunc == null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Index function not found for index name: " + indexName);
|
|
||||||
}
|
|
||||||
return indexFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(String indexName, T obj) {
|
|
||||||
IndexFunc<T> indexFunc = getIndexFunc(indexName);
|
|
||||||
Set<String> indexKeys = indexFunc.apply(obj);
|
|
||||||
Set<String> oldIndexKeys = new HashSet<>();
|
|
||||||
SetMultimap<String, String> indexValue = indexValues.get(indexName);
|
|
||||||
if (indexValue.containsKey(getObjectKey(obj))) {
|
|
||||||
oldIndexKeys.addAll(indexValue.get(getObjectKey(obj)));
|
|
||||||
}
|
|
||||||
// delete old index first
|
|
||||||
for (String oldIndexKey : oldIndexKeys) {
|
|
||||||
SetMultimap<String, String> index = indices.get(indexName);
|
|
||||||
index.remove(oldIndexKey, getObjectKey(obj));
|
|
||||||
indexValue.remove(getObjectKey(obj), oldIndexKey);
|
|
||||||
}
|
|
||||||
// add new index
|
|
||||||
for (String indexKey : indexKeys) {
|
|
||||||
SetMultimap<String, String> index = indices.get(indexName);
|
|
||||||
index.put(indexKey, getObjectKey(obj));
|
|
||||||
|
|
||||||
indexValue.put(getObjectKey(obj), indexKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getByIndex(String indexName, String indexKey) {
|
|
||||||
SetMultimap<String, String> index = indices.get(indexName);
|
|
||||||
if (index != null) {
|
|
||||||
return Set.copyOf(index.get(indexKey));
|
|
||||||
}
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void delete(String indexName, T obj) {
|
|
||||||
IndexFunc<T> indexFunc = getIndexFunc(indexName);
|
|
||||||
SetMultimap<String, String> indexValue = indexValues.get(indexName);
|
|
||||||
Set<String> indexKeys = indexFunc.apply(obj);
|
|
||||||
for (String indexKey : indexKeys) {
|
|
||||||
String objectKey = getObjectKey(obj);
|
|
||||||
SetMultimap<String, String> index = indices.get(indexName);
|
|
||||||
index.remove(indexKey, objectKey);
|
|
||||||
|
|
||||||
indexValue.remove(indexKey, objectKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is only used for testing.
|
|
||||||
*
|
|
||||||
* @param indexName index name
|
|
||||||
* @return all indices of the given index name
|
|
||||||
*/
|
|
||||||
public Map<String, Collection<String>> getIndices(String indexName) {
|
|
||||||
return indices.get(indexName).asMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getObjectKey(T obj) {
|
|
||||||
Assert.notNull(obj, "Object must not be null");
|
|
||||||
Assert.notNull(obj.getMetadata(), "Object metadata must not be null");
|
|
||||||
Assert.notNull(obj.getMetadata().getName(), "Object name must not be null");
|
|
||||||
return obj.getMetadata().getName();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
package run.halo.app.content;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import run.halo.app.extension.Extension;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Indexer is used to index objects by index name and index key.</p>
|
|
||||||
* <p>For example, if you want to index posts by category, you can use the following code:</p>
|
|
||||||
* <pre>
|
|
||||||
* Indexer<Post> indexer = new Indexer<>();
|
|
||||||
* indexer.addIndexFunc("category", post -> {
|
|
||||||
* List<String> tags = post.getSpec().getTags();
|
|
||||||
* return tags == null ? Set.of() : Set.copyOf(tags);
|
|
||||||
* });
|
|
||||||
* indexer.add("category", post);
|
|
||||||
* indexer.getByIndex("category", "category-slug");
|
|
||||||
* indexer.update("category", post);
|
|
||||||
* indexer.delete("category", post);
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param <T> the type of object to be indexed
|
|
||||||
* @author guqing
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
public interface Indexer<T extends Extension> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an index function for a given index name.
|
|
||||||
*
|
|
||||||
* @param indexName The name of the index.
|
|
||||||
* @param indexFunc The function to use for indexing.
|
|
||||||
*/
|
|
||||||
void addIndexFunc(String indexName, DefaultIndexer.IndexFunc<T> indexFunc);
|
|
||||||
|
|
||||||
Set<String> indexNames();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@code add} method adds an object of type T to the index
|
|
||||||
* with the given name. It does this by first getting the index function for the given index
|
|
||||||
* name and applying it to the object to get a set of index keys. For each index key, it adds
|
|
||||||
* the object key to the index and the index key to the object's index values.
|
|
||||||
*
|
|
||||||
* <p>For example, if you want to index Person objects by name and age, you can use the
|
|
||||||
* following:</p>
|
|
||||||
* <pre>
|
|
||||||
* // Create an Indexer that indexes Person objects by name and age
|
|
||||||
* Indexer<Person> indexer = new Indexer<>();
|
|
||||||
* indexer.addIndexFunc("name", person -> Collections.singleton(person.getName()));
|
|
||||||
* indexer.addIndexFunc("age", person -> Collections.singleton(String.valueOf(person
|
|
||||||
* .getAge())));
|
|
||||||
*
|
|
||||||
* // Create some Person objects
|
|
||||||
* Person alice = new Person("Alice", 25);
|
|
||||||
* Person bob = new Person("Bob", 30);
|
|
||||||
*
|
|
||||||
* // Add the Person objects to the index
|
|
||||||
* indexer.add("name", alice);
|
|
||||||
* indexer.add("name", bob);
|
|
||||||
* indexer.add("age", alice);
|
|
||||||
* indexer.add("age", bob);
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param indexName The name of the index.
|
|
||||||
* @param obj The function to use for indexing.
|
|
||||||
* @throws IllegalArgumentException if the index name is not found.
|
|
||||||
*/
|
|
||||||
void add(String indexName, T obj);
|
|
||||||
|
|
||||||
void update(String indexName, T obj);
|
|
||||||
|
|
||||||
Set<String> getByIndex(String indexName, String indexKey);
|
|
||||||
|
|
||||||
void delete(String indexName, T obj);
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
interface IndexFunc<T> {
|
|
||||||
Set<String> apply(T obj);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,221 +0,0 @@
|
||||||
package run.halo.app.content;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.locks.StampedLock;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import org.springframework.beans.factory.DisposableBean;
|
|
||||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import run.halo.app.core.extension.content.Post;
|
|
||||||
import run.halo.app.extension.DefaultExtensionMatcher;
|
|
||||||
import run.halo.app.extension.Extension;
|
|
||||||
import run.halo.app.extension.ExtensionClient;
|
|
||||||
import run.halo.app.extension.GroupVersionKind;
|
|
||||||
import run.halo.app.extension.Metadata;
|
|
||||||
import run.halo.app.extension.MetadataUtil;
|
|
||||||
import run.halo.app.extension.Unstructured;
|
|
||||||
import run.halo.app.extension.Watcher;
|
|
||||||
import run.halo.app.extension.controller.RequestSynchronizer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Monitor changes to {@link Post} resources and establish a local, in-memory cache in an
|
|
||||||
* Indexer.
|
|
||||||
* When changes to posts are detected, the Indexer is updated using the indexFunc to maintain
|
|
||||||
* its integrity.
|
|
||||||
* This enables quick retrieval of the unique identifier(It is usually {@link Metadata#getName()})
|
|
||||||
* for article objects using the getByIndex method when needed.</p>
|
|
||||||
*
|
|
||||||
* @author guqing
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class PostIndexInformer implements ApplicationListener<ApplicationStartedEvent>,
|
|
||||||
DisposableBean {
|
|
||||||
public static final String TAG_POST_INDEXER = "tag-post-indexer";
|
|
||||||
public static final String LABEL_INDEXER_NAME = "post-label-indexer";
|
|
||||||
|
|
||||||
private final RequestSynchronizer synchronizer;
|
|
||||||
|
|
||||||
private final Indexer<Post> postIndexer;
|
|
||||||
|
|
||||||
private final PostWatcher postWatcher;
|
|
||||||
|
|
||||||
public PostIndexInformer(ExtensionClient client) {
|
|
||||||
postIndexer = new DefaultIndexer<>();
|
|
||||||
postIndexer.addIndexFunc(TAG_POST_INDEXER, post -> {
|
|
||||||
List<String> tags = post.getSpec().getTags();
|
|
||||||
return tags != null ? Set.copyOf(tags) : Set.of();
|
|
||||||
});
|
|
||||||
postIndexer.addIndexFunc(LABEL_INDEXER_NAME, labelIndexFunc());
|
|
||||||
|
|
||||||
this.postWatcher = new PostWatcher();
|
|
||||||
var emptyPost = new Post();
|
|
||||||
this.synchronizer = new RequestSynchronizer(true,
|
|
||||||
client,
|
|
||||||
emptyPost,
|
|
||||||
postWatcher,
|
|
||||||
DefaultExtensionMatcher.builder(client, emptyPost.groupVersionKind()).build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DefaultIndexer.IndexFunc<Post> labelIndexFunc() {
|
|
||||||
return post -> {
|
|
||||||
Map<String, String> labels = MetadataUtil.nullSafeLabels(post);
|
|
||||||
Set<String> indexKeys = new HashSet<>();
|
|
||||||
for (Map.Entry<String, String> entry : labels.entrySet()) {
|
|
||||||
indexKeys.add(labelKey(entry.getKey(), entry.getValue()));
|
|
||||||
}
|
|
||||||
return indexKeys;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getByTagName(String tagName) {
|
|
||||||
return postIndexer.getByIndex(TAG_POST_INDEXER, tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getByLabels(Map<String, String> labels) {
|
|
||||||
if (labels == null) {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
Set<String> result = new HashSet<>();
|
|
||||||
for (Map.Entry<String, String> entry : labels.entrySet()) {
|
|
||||||
Set<String> values = postIndexer.getByIndex(LABEL_INDEXER_NAME,
|
|
||||||
labelKey(entry.getKey(), entry.getValue()));
|
|
||||||
if (values == null) {
|
|
||||||
// No objects have this label, no need to continue searching
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
result.addAll(values);
|
|
||||||
} else {
|
|
||||||
result.retainAll(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String labelKey(String labelName, String labelValue) {
|
|
||||||
return labelName + "=" + labelValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() throws Exception {
|
|
||||||
if (postWatcher != null) {
|
|
||||||
postWatcher.dispose();
|
|
||||||
}
|
|
||||||
if (synchronizer != null) {
|
|
||||||
synchronizer.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplicationEvent(@NonNull ApplicationStartedEvent event) {
|
|
||||||
if (!synchronizer.isStarted()) {
|
|
||||||
synchronizer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PostWatcher implements Watcher {
|
|
||||||
private Runnable disposeHook;
|
|
||||||
private boolean disposed = false;
|
|
||||||
private final StampedLock lock = new StampedLock();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAdd(Extension extension) {
|
|
||||||
if (!checkExtension(extension)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleIndicates(extension, postIndexer::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpdate(Extension oldExt, Extension newExt) {
|
|
||||||
if (!checkExtension(newExt)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleIndicates(newExt, postIndexer::update);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDelete(Extension extension) {
|
|
||||||
if (!checkExtension(extension)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleIndicates(extension, postIndexer::delete);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerDisposeHook(Runnable dispose) {
|
|
||||||
this.disposeHook = dispose;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
if (isDisposed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.disposed = true;
|
|
||||||
if (this.disposeHook != null) {
|
|
||||||
this.disposeHook.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDisposed() {
|
|
||||||
return this.disposed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleIndicates(Extension extension, BiConsumer<String, Post> consumer) {
|
|
||||||
Post post = convertTo(extension);
|
|
||||||
Set<String> indexNames = getIndexNames();
|
|
||||||
for (String indexName : indexNames) {
|
|
||||||
maintainIndicates(indexName, post, consumer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> getIndexNames() {
|
|
||||||
long stamp = lock.tryOptimisticRead();
|
|
||||||
Set<String> indexNames = postIndexer.indexNames();
|
|
||||||
if (!lock.validate(stamp)) {
|
|
||||||
stamp = lock.readLock();
|
|
||||||
try {
|
|
||||||
return postIndexer.indexNames();
|
|
||||||
} finally {
|
|
||||||
lock.unlockRead(stamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return indexNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
void maintainIndicates(String indexName, Post post, BiConsumer<String, Post> consumer) {
|
|
||||||
long stamp = lock.writeLock();
|
|
||||||
try {
|
|
||||||
consumer.accept(indexName, post);
|
|
||||||
} finally {
|
|
||||||
lock.unlockWrite(stamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Post convertTo(Extension extension) {
|
|
||||||
if (extension instanceof Post) {
|
|
||||||
return (Post) extension;
|
|
||||||
}
|
|
||||||
return Unstructured.OBJECT_MAPPER.convertValue(extension, Post.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkExtension(Extension extension) {
|
|
||||||
return !postWatcher.isDisposed()
|
|
||||||
&& extension.getMetadata().getDeletionTimestamp() == null
|
|
||||||
&& isPost(extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPost(Extension extension) {
|
|
||||||
return GroupVersionKind.fromExtension(Post.class).equals(extension.groupVersionKind());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +1,23 @@
|
||||||
package run.halo.app.content;
|
package run.halo.app.content;
|
||||||
|
|
||||||
import static java.util.Comparator.comparing;
|
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.ArrayList;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.endpoint.SortResolver;
|
import run.halo.app.core.extension.endpoint.SortResolver;
|
||||||
import run.halo.app.extension.Comparators;
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
import run.halo.app.extension.router.IListRequest;
|
import run.halo.app.extension.router.IListRequest;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
|
import run.halo.app.extension.router.selector.LabelSelector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A query object for {@link Post} list.
|
* A query object for {@link Post} list.
|
||||||
|
@ -52,36 +47,12 @@ public class PostQuery extends IListRequest.QueryListRequest {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Schema(name = "contributor")
|
|
||||||
public Set<String> getContributors() {
|
|
||||||
return listToSet(queryParams.get("contributor"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Schema(name = "category")
|
|
||||||
public Set<String> getCategories() {
|
|
||||||
return listToSet(queryParams.get("category"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Schema(name = "tag")
|
|
||||||
public Set<String> getTags() {
|
|
||||||
return listToSet(queryParams.get("tag"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Post.PostPhase getPublishPhase() {
|
public Post.PostPhase getPublishPhase() {
|
||||||
String publishPhase = queryParams.getFirst("publishPhase");
|
String publishPhase = queryParams.getFirst("publishPhase");
|
||||||
return Post.PostPhase.from(publishPhase);
|
return Post.PostPhase.from(publishPhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Post.VisibleEnum getVisible() {
|
|
||||||
String visible = queryParams.getFirst("visible");
|
|
||||||
return Post.VisibleEnum.from(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Schema(description = "Posts filtered by keyword.")
|
@Schema(description = "Posts filtered by keyword.")
|
||||||
public String getKeyword() {
|
public String getKeyword() {
|
||||||
|
@ -96,127 +67,58 @@ public class PostQuery extends IListRequest.QueryListRequest {
|
||||||
implementation = String.class,
|
implementation = String.class,
|
||||||
example = "creationTimestamp,desc"))
|
example = "creationTimestamp,desc"))
|
||||||
public Sort getSort() {
|
public Sort getSort() {
|
||||||
return SortResolver.defaultInstance.resolve(exchange);
|
var sort = SortResolver.defaultInstance.resolve(exchange);
|
||||||
}
|
sort = sort.and(Sort.by(Sort.Direction.DESC, "metadata.creationTimestamp"));
|
||||||
|
sort = sort.and(Sort.by(Sort.Direction.DESC, "metadata.name"));
|
||||||
@Nullable
|
return sort;
|
||||||
private Set<String> listToSet(List<String> param) {
|
|
||||||
return param == null ? null : Set.copyOf(param);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a comparator from the query object.
|
* Build a list options from the query object.
|
||||||
*
|
*
|
||||||
* @return a comparator
|
* @return a list options
|
||||||
*/
|
*/
|
||||||
public Comparator<Post> toComparator() {
|
public ListOptions toListOptions() {
|
||||||
var sort = getSort();
|
var listOptions =
|
||||||
var creationTimestampOrder = sort.getOrderFor("creationTimestamp");
|
labelAndFieldSelectorToListOptions(getLabelSelector(), getFieldSelector());
|
||||||
List<Comparator<Post>> comparators = new ArrayList<>();
|
if (listOptions.getFieldSelector() == null) {
|
||||||
if (creationTimestampOrder != null) {
|
listOptions.setFieldSelector(FieldSelector.all());
|
||||||
Comparator<Post> comparator =
|
|
||||||
comparing(post -> post.getMetadata().getCreationTimestamp());
|
|
||||||
if (creationTimestampOrder.isDescending()) {
|
|
||||||
comparator = comparator.reversed();
|
|
||||||
}
|
|
||||||
comparators.add(comparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
var publishTimeOrder = sort.getOrderFor("publishTime");
|
|
||||||
if (publishTimeOrder != null) {
|
|
||||||
Comparator<Object> nullsComparator = publishTimeOrder.isAscending()
|
|
||||||
? org.springframework.util.comparator.Comparators.nullsLow()
|
|
||||||
: org.springframework.util.comparator.Comparators.nullsHigh();
|
|
||||||
Comparator<Post> comparator =
|
|
||||||
comparing(post -> post.getSpec().getPublishTime(), nullsComparator);
|
|
||||||
if (publishTimeOrder.isDescending()) {
|
|
||||||
comparator = comparator.reversed();
|
|
||||||
}
|
|
||||||
comparators.add(comparator);
|
|
||||||
}
|
|
||||||
comparators.add(Comparators.compareCreationTimestamp(false));
|
|
||||||
comparators.add(Comparators.compareName(true));
|
|
||||||
return comparators.stream()
|
|
||||||
.reduce(Comparator::thenComparing)
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a predicate from the query object.
|
|
||||||
*
|
|
||||||
* @return a predicate
|
|
||||||
*/
|
|
||||||
public Predicate<Post> toPredicate() {
|
|
||||||
Predicate<Post> predicate = labelAndFieldSelectorToPredicate(getLabelSelector(),
|
|
||||||
getFieldSelector());
|
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(getCategories())) {
|
|
||||||
predicate =
|
|
||||||
predicate.and(post -> contains(getCategories(), post.getSpec().getCategories()));
|
|
||||||
}
|
|
||||||
if (!CollectionUtils.isEmpty(getTags())) {
|
|
||||||
predicate = predicate.and(post -> contains(getTags(), post.getSpec().getTags()));
|
|
||||||
}
|
|
||||||
if (!CollectionUtils.isEmpty(getContributors())) {
|
|
||||||
Predicate<Post> hasStatus = post -> post.getStatus() != null;
|
|
||||||
var containsContributors = hasStatus.and(
|
|
||||||
post -> contains(getContributors(), post.getStatus().getContributors())
|
|
||||||
);
|
|
||||||
predicate = predicate.and(containsContributors);
|
|
||||||
}
|
}
|
||||||
|
var labelSelectorBuilder = LabelSelector.builder();
|
||||||
|
var fieldQuery = QueryFactory.all();
|
||||||
|
|
||||||
String keyword = getKeyword();
|
String keyword = getKeyword();
|
||||||
if (keyword != null) {
|
if (keyword != null) {
|
||||||
predicate = predicate.and(post -> {
|
fieldQuery = QueryFactory.and(fieldQuery, QueryFactory.or(
|
||||||
String excerpt = post.getStatusOrDefault().getExcerpt();
|
QueryFactory.contains("status.excerpt", keyword),
|
||||||
return StringUtils.containsIgnoreCase(excerpt, keyword)
|
QueryFactory.contains("spec.slug", keyword),
|
||||||
|| StringUtils.containsIgnoreCase(post.getSpec().getSlug(), keyword)
|
QueryFactory.contains("spec.title", keyword)
|
||||||
|| StringUtils.containsIgnoreCase(post.getSpec().getTitle(), keyword);
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Post.PostPhase publishPhase = getPublishPhase();
|
Post.PostPhase publishPhase = getPublishPhase();
|
||||||
if (publishPhase != null) {
|
if (publishPhase != null) {
|
||||||
predicate = predicate.and(post -> {
|
if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) {
|
||||||
if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) {
|
fieldQuery = QueryFactory.and(fieldQuery, QueryFactory.equal(
|
||||||
return !post.isPublished()
|
"status.phase", Post.PostPhase.PENDING_APPROVAL.name())
|
||||||
&& Post.PostPhase.PENDING_APPROVAL.name()
|
);
|
||||||
.equalsIgnoreCase(post.getStatusOrDefault().getPhase());
|
labelSelectorBuilder.eq(Post.PUBLISHED_LABEL, BooleanUtils.FALSE);
|
||||||
}
|
} else if (Post.PostPhase.PUBLISHED.equals(publishPhase)) {
|
||||||
// published
|
labelSelectorBuilder.eq(Post.PUBLISHED_LABEL, BooleanUtils.TRUE);
|
||||||
if (Post.PostPhase.PUBLISHED.equals(publishPhase)) {
|
} else {
|
||||||
return post.isPublished();
|
labelSelectorBuilder.eq(Post.PUBLISHED_LABEL, BooleanUtils.FALSE);
|
||||||
}
|
}
|
||||||
// draft
|
|
||||||
return !post.isPublished();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Post.VisibleEnum visible = getVisible();
|
|
||||||
if (visible != null) {
|
|
||||||
predicate =
|
|
||||||
predicate.and(post -> visible.equals(post.getSpec().getVisible()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(username)) {
|
if (StringUtils.isNotBlank(username)) {
|
||||||
Predicate<Post> isOwner = post -> Objects.equals(username, post.getSpec().getOwner());
|
fieldQuery = QueryFactory.and(fieldQuery, QueryFactory.equal(
|
||||||
predicate = predicate.and(isOwner);
|
"spec.owner", username)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return predicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean contains(Collection<String> left, List<String> right) {
|
listOptions.setFieldSelector(listOptions.getFieldSelector().andQuery(fieldQuery));
|
||||||
// parameter is null, it means that ignore this condition
|
listOptions.setLabelSelector(
|
||||||
if (left == null) {
|
listOptions.getLabelSelector().and(labelSelectorBuilder.build()));
|
||||||
return true;
|
return listOptions;
|
||||||
}
|
|
||||||
// else, it means that right is empty
|
|
||||||
if (left.isEmpty()) {
|
|
||||||
return right.isEmpty();
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return right.stream().anyMatch(left::contains);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content.impl;
|
package run.halo.app.content.impl;
|
||||||
|
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.in;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -8,6 +10,7 @@ import java.util.function.Function;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.dao.OptimisticLockingFailureException;
|
import org.springframework.dao.OptimisticLockingFailureException;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -27,9 +30,12 @@ import run.halo.app.core.extension.content.Category;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
import run.halo.app.core.extension.service.UserService;
|
import run.halo.app.core.extension.service.UserService;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.Ref;
|
import run.halo.app.extension.Ref;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
import run.halo.app.infra.Condition;
|
import run.halo.app.infra.Condition;
|
||||||
import run.halo.app.infra.ConditionStatus;
|
import run.halo.app.infra.ConditionStatus;
|
||||||
import run.halo.app.metrics.CounterService;
|
import run.halo.app.metrics.CounterService;
|
||||||
|
@ -58,16 +64,17 @@ public class PostServiceImpl extends AbstractContentService implements PostServi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPost>> listPost(PostQuery query) {
|
public Mono<ListResult<ListedPost>> listPost(PostQuery query) {
|
||||||
return client.list(Post.class, query.toPredicate(),
|
return client.listBy(Post.class, query.toListOptions(),
|
||||||
query.toComparator(), query.getPage(), query.getSize())
|
PageRequestImpl.of(query.getPage(), query.getSize(), query.getSort())
|
||||||
.flatMap(listResult -> Flux.fromStream(
|
)
|
||||||
listResult.get().map(this::getListedPost)
|
.flatMap(listResult -> Flux.fromStream(listResult.get())
|
||||||
)
|
.map(this::getListedPost)
|
||||||
.concatMap(Function.identity())
|
.concatMap(Function.identity())
|
||||||
.collectList()
|
.collectList()
|
||||||
.map(listedPosts -> new ListResult<>(listResult.getPage(), listResult.getSize(),
|
.map(listedPosts -> new ListResult<>(listResult.getPage(), listResult.getSize(),
|
||||||
listResult.getTotal(), listedPosts)
|
listResult.getTotal(), listedPosts)
|
||||||
)
|
)
|
||||||
|
.defaultIfEmpty(ListResult.emptyResult())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,16 +151,18 @@ public class PostServiceImpl extends AbstractContentService implements PostServi
|
||||||
if (tagNames == null) {
|
if (tagNames == null) {
|
||||||
return Flux.empty();
|
return Flux.empty();
|
||||||
}
|
}
|
||||||
return Flux.fromIterable(tagNames)
|
var listOptions = new ListOptions();
|
||||||
.flatMapSequential(tagName -> client.fetch(Tag.class, tagName));
|
listOptions.setFieldSelector(FieldSelector.of(in("metadata.name", tagNames)));
|
||||||
|
return client.listAll(Tag.class, listOptions, Sort.by("metadata.creationTimestamp"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<Category> listCategories(List<String> categoryNames) {
|
private Flux<Category> listCategories(List<String> categoryNames) {
|
||||||
if (categoryNames == null) {
|
if (categoryNames == null) {
|
||||||
return Flux.empty();
|
return Flux.empty();
|
||||||
}
|
}
|
||||||
return Flux.fromIterable(categoryNames)
|
var listOptions = new ListOptions();
|
||||||
.flatMapSequential(categoryName -> client.fetch(Category.class, categoryName));
|
listOptions.setFieldSelector(FieldSelector.of(in("metadata.name", categoryNames)));
|
||||||
|
return client.listAll(Category.class, listOptions, Sort.by("metadata.creationTimestamp"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<Contributor> listContributors(List<String> usernames) {
|
private Flux<Contributor> listContributors(List<String> usernames) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ public class SnapshotServiceImpl implements SnapshotService {
|
||||||
|
|
||||||
private final ReactiveExtensionClient client;
|
private final ReactiveExtensionClient client;
|
||||||
|
|
||||||
private Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
public SnapshotServiceImpl(ReactiveExtensionClient client) {
|
public SnapshotServiceImpl(ReactiveExtensionClient client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
|
@ -2,6 +2,9 @@ package run.halo.app.core.extension.endpoint;
|
||||||
|
|
||||||
import static java.lang.Boolean.parseBoolean;
|
import static java.lang.Boolean.parseBoolean;
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
|
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 lombok.Data;
|
import lombok.Data;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
|
@ -13,8 +16,11 @@ import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.Counter;
|
import run.halo.app.core.extension.Counter;
|
||||||
import run.halo.app.core.extension.User;
|
import run.halo.app.core.extension.User;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.MetadataUtil;
|
import run.halo.app.extension.MetadataUtil;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stats endpoint.
|
* Stats endpoint.
|
||||||
|
@ -67,13 +73,16 @@ public class StatsEndpoint implements CustomEndpoint {
|
||||||
stats.setUsers(count.intValue());
|
stats.setUsers(count.intValue());
|
||||||
return stats;
|
return stats;
|
||||||
}))
|
}))
|
||||||
.flatMap(stats -> client.list(Post.class, post -> !post.isDeleted(), null)
|
.flatMap(stats -> {
|
||||||
.count()
|
var listOptions = new ListOptions();
|
||||||
.map(count -> {
|
listOptions.setFieldSelector(FieldSelector.of(
|
||||||
stats.setPosts(count.intValue());
|
and(isNull("metadata.deletionTimestamp"),
|
||||||
return stats;
|
equal("spec.deleted", "false")))
|
||||||
})
|
);
|
||||||
)
|
return client.listBy(Post.class, listOptions, PageRequestImpl.ofSize(1))
|
||||||
|
.doOnNext(list -> stats.setPosts((int) list.getTotal()))
|
||||||
|
.thenReturn(stats);
|
||||||
|
})
|
||||||
.flatMap(stats -> ServerResponse.ok().bodyValue(stats));
|
.flatMap(stats -> ServerResponse.ok().bodyValue(stats));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package run.halo.app.core.extension.reconciler;
|
package run.halo.app.core.extension.reconciler;
|
||||||
|
|
||||||
|
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.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -13,6 +17,7 @@ import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
|
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
|
||||||
|
@ -20,10 +25,12 @@ import run.halo.app.core.extension.content.Category;
|
||||||
import run.halo.app.core.extension.content.Constant;
|
import run.halo.app.core.extension.content.Constant;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.MetadataUtil;
|
import run.halo.app.extension.MetadataUtil;
|
||||||
import run.halo.app.extension.controller.Controller;
|
import run.halo.app.extension.controller.Controller;
|
||||||
import run.halo.app.extension.controller.ControllerBuilder;
|
import run.halo.app.extension.controller.ControllerBuilder;
|
||||||
import run.halo.app.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,7 +145,12 @@ public class CategoryReconciler implements Reconciler<Reconciler.Request> {
|
||||||
.map(item -> item.getMetadata().getName())
|
.map(item -> item.getMetadata().getName())
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
List<Post> posts = client.list(Post.class, post -> !post.isDeleted(), null);
|
var postListOptions = new ListOptions();
|
||||||
|
postListOptions.setFieldSelector(FieldSelector.of(
|
||||||
|
and(isNull("metadata.deletionTimestamp"),
|
||||||
|
equal("spec.deleted", "false")))
|
||||||
|
);
|
||||||
|
var posts = client.listAll(Post.class, postListOptions, Sort.unsorted());
|
||||||
|
|
||||||
// populate post to status
|
// populate post to status
|
||||||
List<Post.CompactPost> compactPosts = posts.stream()
|
List<Post.CompactPost> compactPosts = posts.stream()
|
||||||
|
@ -178,7 +190,7 @@ public class CategoryReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Category> listChildrenByName(String name) {
|
private List<Category> listChildrenByName(String name) {
|
||||||
List<Category> categories = client.list(Category.class, null, null);
|
var categories = client.listAll(Category.class, new ListOptions(), Sort.unsorted());
|
||||||
Map<String, Category> nameIdentityMap = categories.stream()
|
Map<String, Category> nameIdentityMap = categories.stream()
|
||||||
.collect(Collectors.toMap(category -> category.getMetadata().getName(),
|
.collect(Collectors.toMap(category -> category.getMetadata().getName(),
|
||||||
Function.identity()));
|
Function.identity()));
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.google.common.hash.Hashing;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -18,6 +19,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import run.halo.app.content.ContentWrapper;
|
import run.halo.app.content.ContentWrapper;
|
||||||
import run.halo.app.content.NotificationReasonConst;
|
import run.halo.app.content.NotificationReasonConst;
|
||||||
|
@ -36,10 +38,13 @@ import run.halo.app.event.post.PostUpdatedEvent;
|
||||||
import run.halo.app.event.post.PostVisibleChangedEvent;
|
import run.halo.app.event.post.PostVisibleChangedEvent;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.extension.ExtensionOperator;
|
import run.halo.app.extension.ExtensionOperator;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.Ref;
|
import run.halo.app.extension.Ref;
|
||||||
import run.halo.app.extension.controller.Controller;
|
import run.halo.app.extension.controller.Controller;
|
||||||
import run.halo.app.extension.controller.ControllerBuilder;
|
import run.halo.app.extension.controller.ControllerBuilder;
|
||||||
import run.halo.app.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
import run.halo.app.infra.Condition;
|
import run.halo.app.infra.Condition;
|
||||||
import run.halo.app.infra.ConditionStatus;
|
import run.halo.app.infra.ConditionStatus;
|
||||||
import run.halo.app.infra.utils.HaloUtils;
|
import run.halo.app.infra.utils.HaloUtils;
|
||||||
|
@ -189,8 +194,7 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
var ref = Ref.of(post);
|
var ref = Ref.of(post);
|
||||||
// handle contributors
|
// handle contributors
|
||||||
var headSnapshot = post.getSpec().getHeadSnapshot();
|
var headSnapshot = post.getSpec().getHeadSnapshot();
|
||||||
var contributors = client.list(Snapshot.class,
|
var contributors = listSnapshots(ref)
|
||||||
snapshot -> ref.equals(snapshot.getSpec().getSubjectRef()), null)
|
|
||||||
.stream()
|
.stream()
|
||||||
.map(snapshot -> {
|
.map(snapshot -> {
|
||||||
Set<String> usernames = snapshot.getSpec().getContributors();
|
Set<String> usernames = snapshot.getSpec().getContributors();
|
||||||
|
@ -292,7 +296,7 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
var labels = post.getMetadata().getLabels();
|
var labels = post.getMetadata().getLabels();
|
||||||
labels.put(Post.PUBLISHED_LABEL, Boolean.FALSE.toString());
|
labels.put(Post.PUBLISHED_LABEL, Boolean.FALSE.toString());
|
||||||
var status = post.getStatus();
|
final var status = post.getStatus();
|
||||||
|
|
||||||
var condition = new Condition();
|
var condition = new Condition();
|
||||||
condition.setType("CancelledPublish");
|
condition.setType("CancelledPublish");
|
||||||
|
@ -310,9 +314,7 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
private void cleanUpResources(Post post) {
|
private void cleanUpResources(Post post) {
|
||||||
// clean up snapshots
|
// clean up snapshots
|
||||||
final Ref ref = Ref.of(post);
|
final Ref ref = Ref.of(post);
|
||||||
client.list(Snapshot.class,
|
listSnapshots(ref).forEach(client::delete);
|
||||||
snapshot -> ref.equals(snapshot.getSpec().getSubjectRef()), null)
|
|
||||||
.forEach(client::delete);
|
|
||||||
|
|
||||||
// clean up comments
|
// clean up comments
|
||||||
client.list(Comment.class, comment -> ref.equals(comment.getSpec().getSubjectRef()),
|
client.list(Comment.class, comment -> ref.equals(comment.getSpec().getSubjectRef()),
|
||||||
|
@ -330,4 +332,11 @@ public class PostReconciler implements Reconciler<Reconciler.Request> {
|
||||||
// TODO The default capture 150 words as excerpt
|
// TODO The default capture 150 words as excerpt
|
||||||
return StringUtils.substring(text, 0, 150);
|
return StringUtils.substring(text, 0, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Snapshot> listSnapshots(Ref ref) {
|
||||||
|
var snapshotListOptions = new ListOptions();
|
||||||
|
snapshotListOptions.setFieldSelector(FieldSelector.of(
|
||||||
|
QueryFactory.equal("spec.subjectRef", Snapshot.toSubjectRefKey(ref))));
|
||||||
|
return client.listAll(Snapshot.class, snapshotListOptions, Sort.unsorted());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import run.halo.app.content.NotificationReasonConst;
|
import run.halo.app.content.NotificationReasonConst;
|
||||||
|
@ -26,11 +27,14 @@ import run.halo.app.core.extension.notification.Subscription;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.extension.ExtensionOperator;
|
import run.halo.app.extension.ExtensionOperator;
|
||||||
import run.halo.app.extension.ExtensionUtil;
|
import run.halo.app.extension.ExtensionUtil;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.MetadataUtil;
|
import run.halo.app.extension.MetadataUtil;
|
||||||
import run.halo.app.extension.Ref;
|
import run.halo.app.extension.Ref;
|
||||||
import run.halo.app.extension.controller.Controller;
|
import run.halo.app.extension.controller.Controller;
|
||||||
import run.halo.app.extension.controller.ControllerBuilder;
|
import run.halo.app.extension.controller.ControllerBuilder;
|
||||||
import run.halo.app.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
import run.halo.app.infra.Condition;
|
import run.halo.app.infra.Condition;
|
||||||
import run.halo.app.infra.ConditionList;
|
import run.halo.app.infra.ConditionList;
|
||||||
import run.halo.app.infra.ConditionStatus;
|
import run.halo.app.infra.ConditionStatus;
|
||||||
|
@ -243,9 +247,7 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
||||||
private void cleanUpResources(SinglePage singlePage) {
|
private void cleanUpResources(SinglePage singlePage) {
|
||||||
// clean up snapshot
|
// clean up snapshot
|
||||||
Ref ref = Ref.of(singlePage);
|
Ref ref = Ref.of(singlePage);
|
||||||
client.list(Snapshot.class,
|
listSnapshots(ref).forEach(client::delete);
|
||||||
snapshot -> ref.equals(snapshot.getSpec().getSubjectRef()), null)
|
|
||||||
.forEach(client::delete);
|
|
||||||
|
|
||||||
// clean up comments
|
// clean up comments
|
||||||
client.list(Comment.class, comment -> comment.getSpec().getSubjectRef().equals(ref),
|
client.list(Comment.class, comment -> comment.getSpec().getSubjectRef().equals(ref),
|
||||||
|
@ -332,8 +334,7 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
||||||
|
|
||||||
// handle contributors
|
// handle contributors
|
||||||
String headSnapshot = singlePage.getSpec().getHeadSnapshot();
|
String headSnapshot = singlePage.getSpec().getHeadSnapshot();
|
||||||
List<String> contributors = client.list(Snapshot.class,
|
List<String> contributors = listSnapshots(Ref.of(singlePage))
|
||||||
snapshot -> Ref.of(singlePage).equals(snapshot.getSpec().getSubjectRef()), null)
|
|
||||||
.stream()
|
.stream()
|
||||||
.peek(snapshot -> {
|
.peek(snapshot -> {
|
||||||
snapshot.getSpec().setContentPatch(StringUtils.EMPTY);
|
snapshot.getSpec().setContentPatch(StringUtils.EMPTY);
|
||||||
|
@ -377,4 +378,11 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
||||||
return Objects.equals(true, singlePage.getSpec().getDeleted())
|
return Objects.equals(true, singlePage.getSpec().getDeleted())
|
||||||
|| singlePage.getMetadata().getDeletionTimestamp() != null;
|
|| singlePage.getMetadata().getDeletionTimestamp() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Snapshot> listSnapshots(Ref ref) {
|
||||||
|
var snapshotListOptions = new ListOptions();
|
||||||
|
snapshotListOptions.setFieldSelector(FieldSelector.of(
|
||||||
|
QueryFactory.equal("spec.subjectRef", Snapshot.toSubjectRefKey(ref))));
|
||||||
|
return client.listAll(Snapshot.class, snapshotListOptions, Sort.unsorted());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package run.halo.app.core.extension.reconciler;
|
package run.halo.app.core.extension.reconciler;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.BooleanUtils.isFalse;
|
||||||
|
import static run.halo.app.extension.MetadataUtil.nullSafeLabels;
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -7,17 +11,19 @@ import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import run.halo.app.content.PostIndexInformer;
|
|
||||||
import run.halo.app.content.permalinks.TagPermalinkPolicy;
|
import run.halo.app.content.permalinks.TagPermalinkPolicy;
|
||||||
import run.halo.app.core.extension.content.Constant;
|
import run.halo.app.core.extension.content.Constant;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.MetadataUtil;
|
import run.halo.app.extension.MetadataUtil;
|
||||||
import run.halo.app.extension.controller.Controller;
|
import run.halo.app.extension.controller.Controller;
|
||||||
import run.halo.app.extension.controller.ControllerBuilder;
|
import run.halo.app.extension.controller.ControllerBuilder;
|
||||||
import run.halo.app.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +38,6 @@ public class TagReconciler implements Reconciler<Reconciler.Request> {
|
||||||
private static final String FINALIZER_NAME = "tag-protection";
|
private static final String FINALIZER_NAME = "tag-protection";
|
||||||
private final ExtensionClient client;
|
private final ExtensionClient client;
|
||||||
private final TagPermalinkPolicy tagPermalinkPolicy;
|
private final TagPermalinkPolicy tagPermalinkPolicy;
|
||||||
private final PostIndexInformer postIndexInformer;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result reconcile(Request request) {
|
public Result reconcile(Request request) {
|
||||||
|
@ -128,20 +133,22 @@ public class TagReconciler implements Reconciler<Reconciler.Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populatePosts(Tag tag) {
|
private void populatePosts(Tag tag) {
|
||||||
// populate post count
|
// populate post-count
|
||||||
Set<String> postNames = postIndexInformer.getByTagName(tag.getMetadata().getName());
|
var listOptions = new ListOptions();
|
||||||
tag.getStatusOrDefault().setPostCount(postNames.size());
|
listOptions.setFieldSelector(FieldSelector.of(
|
||||||
|
equal("spec.tags", tag.getMetadata().getName()))
|
||||||
|
);
|
||||||
|
var posts = client.listAll(Post.class, listOptions, Sort.unsorted());
|
||||||
|
tag.getStatusOrDefault().setPostCount(posts.size());
|
||||||
|
|
||||||
// populate visible post count
|
var publicPosts = posts.stream()
|
||||||
Map<String, String> labelToQuery = Map.of(Post.PUBLISHED_LABEL, BooleanUtils.TRUE,
|
.filter(post -> post.getMetadata().getDeletionTimestamp() == null
|
||||||
Post.VISIBLE_LABEL, Post.VisibleEnum.PUBLIC.name(),
|
&& isFalse(post.getSpec().getDeleted())
|
||||||
Post.DELETED_LABEL, BooleanUtils.FALSE);
|
&& BooleanUtils.TRUE.equals(nullSafeLabels(post).get(Post.PUBLISHED_LABEL))
|
||||||
Set<String> hasAllLabelPosts = postIndexInformer.getByLabels(labelToQuery);
|
&& Post.VisibleEnum.PUBLIC.equals(post.getSpec().getVisible())
|
||||||
|
)
|
||||||
// retain all posts that has all labels
|
.toList();
|
||||||
Set<String> postNamesWithTag = new HashSet<>(postNames);
|
tag.getStatusOrDefault().setVisiblePostCount(publicPosts.size());
|
||||||
postNamesWithTag.retainAll(hasAllLabelPosts);
|
|
||||||
tag.getStatusOrDefault().setVisiblePostCount(postNamesWithTag.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isDeleted(Tag tag) {
|
private boolean isDeleted(Tag tag) {
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package run.halo.app.infra;
|
package run.halo.app.infra;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
import static run.halo.app.extension.index.IndexAttributeFactory.multiValueAttribute;
|
||||||
|
import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
|
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
@ -38,6 +43,7 @@ import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.DefaultSchemeManager;
|
import run.halo.app.extension.DefaultSchemeManager;
|
||||||
import run.halo.app.extension.DefaultSchemeWatcherManager;
|
import run.halo.app.extension.DefaultSchemeWatcherManager;
|
||||||
import run.halo.app.extension.Secret;
|
import run.halo.app.extension.Secret;
|
||||||
|
import run.halo.app.extension.index.IndexSpec;
|
||||||
import run.halo.app.extension.index.IndexSpecRegistryImpl;
|
import run.halo.app.extension.index.IndexSpecRegistryImpl;
|
||||||
import run.halo.app.migration.Backup;
|
import run.halo.app.migration.Backup;
|
||||||
import run.halo.app.plugin.extensionpoint.ExtensionDefinition;
|
import run.halo.app.plugin.extensionpoint.ExtensionDefinition;
|
||||||
|
@ -70,10 +76,102 @@ public class SchemeInitializer implements ApplicationListener<ApplicationContext
|
||||||
schemeManager.register(Theme.class);
|
schemeManager.register(Theme.class);
|
||||||
schemeManager.register(Menu.class);
|
schemeManager.register(Menu.class);
|
||||||
schemeManager.register(MenuItem.class);
|
schemeManager.register(MenuItem.class);
|
||||||
schemeManager.register(Post.class);
|
schemeManager.register(Post.class, indexSpecs -> {
|
||||||
schemeManager.register(Category.class);
|
indexSpecs.add(new IndexSpec()
|
||||||
schemeManager.register(Tag.class);
|
.setName("spec.title")
|
||||||
schemeManager.register(Snapshot.class);
|
.setIndexFunc(simpleAttribute(Post.class, post -> post.getSpec().getTitle())));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.slug")
|
||||||
|
// Compatible with old data, hoping to set it to true in the future
|
||||||
|
.setUnique(false)
|
||||||
|
.setIndexFunc(simpleAttribute(Post.class, post -> post.getSpec().getSlug())));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.publishTime")
|
||||||
|
.setIndexFunc(simpleAttribute(Post.class, post -> {
|
||||||
|
var publishTime = post.getSpec().getPublishTime();
|
||||||
|
return publishTime == null ? null : publishTime.toString();
|
||||||
|
})));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.owner")
|
||||||
|
.setIndexFunc(simpleAttribute(Post.class, post -> post.getSpec().getOwner())));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.deleted")
|
||||||
|
.setIndexFunc(simpleAttribute(Post.class, post -> {
|
||||||
|
var deleted = post.getSpec().getDeleted();
|
||||||
|
return deleted == null ? "false" : deleted.toString();
|
||||||
|
})));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.pinned")
|
||||||
|
.setIndexFunc(simpleAttribute(Post.class, post -> {
|
||||||
|
var pinned = post.getSpec().getPinned();
|
||||||
|
return pinned == null ? "false" : pinned.toString();
|
||||||
|
})));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.priority")
|
||||||
|
.setIndexFunc(simpleAttribute(Post.class, post -> {
|
||||||
|
var priority = post.getSpec().getPriority();
|
||||||
|
return priority == null ? "0" : priority.toString();
|
||||||
|
})));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.visible")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(Post.class, post -> post.getSpec().getVisible().name())));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.tags")
|
||||||
|
.setIndexFunc(multiValueAttribute(Post.class, post -> {
|
||||||
|
var tags = post.getSpec().getTags();
|
||||||
|
return tags == null ? Set.of() : Set.copyOf(tags);
|
||||||
|
})));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.categories")
|
||||||
|
.setIndexFunc(multiValueAttribute(Post.class, post -> {
|
||||||
|
var categories = post.getSpec().getCategories();
|
||||||
|
return categories == null ? Set.of() : Set.copyOf(categories);
|
||||||
|
})));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("status.contributors")
|
||||||
|
.setIndexFunc(multiValueAttribute(Post.class, post -> {
|
||||||
|
var contributors = post.getStatusOrDefault().getContributors();
|
||||||
|
return contributors == null ? Set.of() : Set.copyOf(contributors);
|
||||||
|
})));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("status.categories")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(Post.class, post -> post.getStatusOrDefault().getExcerpt())));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("status.phase")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(Post.class, post -> post.getStatusOrDefault().getPhase())));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("status.excerpt")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(Post.class, post -> post.getStatusOrDefault().getExcerpt())));
|
||||||
|
});
|
||||||
|
schemeManager.register(Category.class, indexSpecs -> {
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.slug")
|
||||||
|
.setIndexFunc(
|
||||||
|
simpleAttribute(Category.class, category -> category.getSpec().getSlug()))
|
||||||
|
);
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.priority")
|
||||||
|
.setIndexFunc(simpleAttribute(Category.class,
|
||||||
|
category -> defaultIfNull(category.getSpec().getPriority(), 0).toString())));
|
||||||
|
});
|
||||||
|
schemeManager.register(Tag.class, indexSpecs -> {
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.slug")
|
||||||
|
.setIndexFunc(simpleAttribute(Tag.class, tag -> tag.getSpec().getSlug()))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
schemeManager.register(Snapshot.class, indexSpecs -> {
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.subjectRef")
|
||||||
|
.setIndexFunc(simpleAttribute(Snapshot.class,
|
||||||
|
snapshot -> Snapshot.toSubjectRefKey(snapshot.getSpec().getSubjectRef()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
schemeManager.register(Comment.class);
|
schemeManager.register(Comment.class);
|
||||||
schemeManager.register(Reply.class);
|
schemeManager.register(Reply.class);
|
||||||
schemeManager.register(SinglePage.class);
|
schemeManager.register(SinglePage.class);
|
||||||
|
|
|
@ -2,11 +2,9 @@ package run.halo.app.theme.endpoint;
|
||||||
|
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
import static run.halo.app.theme.endpoint.PublicApiUtils.containsElement;
|
|
||||||
import static run.halo.app.theme.endpoint.PublicApiUtils.toAnotherListResult;
|
import static run.halo.app.theme.endpoint.PublicApiUtils.toAnotherListResult;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
@ -17,11 +15,11 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
import run.halo.app.core.extension.content.Post;
|
|
||||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||||
import run.halo.app.extension.GroupVersion;
|
import run.halo.app.extension.GroupVersion;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||||
import run.halo.app.extension.router.SortableRequest;
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||||
|
@ -93,13 +91,11 @@ public class CategoryQueryEndpoint implements CustomEndpoint {
|
||||||
private Mono<ServerResponse> listPostsByCategoryName(ServerRequest request) {
|
private Mono<ServerResponse> listPostsByCategoryName(ServerRequest request) {
|
||||||
final var name = request.pathVariable("name");
|
final var name = request.pathVariable("name");
|
||||||
final var query = new PostPublicQuery(request.exchange());
|
final var query = new PostPublicQuery(request.exchange());
|
||||||
Predicate<Post> categoryContainsPredicate =
|
var listOptions = query.toListOptions();
|
||||||
post -> containsElement(post.getSpec().getCategories(), name);
|
var newFieldSelector = listOptions.getFieldSelector()
|
||||||
return postPublicQueryService.list(query.getPage(),
|
.andQuery(QueryFactory.equal("spec.categories", name));
|
||||||
query.getSize(),
|
listOptions.setFieldSelector(newFieldSelector);
|
||||||
categoryContainsPredicate.and(query.toPredicate()),
|
return postPublicQueryService.list(listOptions, query.toPageRequest())
|
||||||
query.toComparator()
|
|
||||||
)
|
|
||||||
.flatMap(result -> ServerResponse.ok()
|
.flatMap(result -> ServerResponse.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.bodyValue(result)
|
.bodyValue(result)
|
||||||
|
@ -118,12 +114,7 @@ public class CategoryQueryEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
private Mono<ServerResponse> listCategories(ServerRequest request) {
|
private Mono<ServerResponse> listCategories(ServerRequest request) {
|
||||||
CategoryPublicQuery query = new CategoryPublicQuery(request.exchange());
|
CategoryPublicQuery query = new CategoryPublicQuery(request.exchange());
|
||||||
return client.list(Category.class,
|
return client.listBy(Category.class, query.toListOptions(), query.toPageRequest())
|
||||||
query.toPredicate(),
|
|
||||||
query.toComparator(),
|
|
||||||
query.getPage(),
|
|
||||||
query.getSize()
|
|
||||||
)
|
|
||||||
.map(listResult -> toAnotherListResult(listResult, CategoryVo::from))
|
.map(listResult -> toAnotherListResult(listResult, CategoryVo::from))
|
||||||
.flatMap(result -> ServerResponse.ok()
|
.flatMap(result -> ServerResponse.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
|
|
@ -107,8 +107,7 @@ public class PostQueryEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
private Mono<ServerResponse> listPosts(ServerRequest request) {
|
private Mono<ServerResponse> listPosts(ServerRequest request) {
|
||||||
PostPublicQuery query = new PostPublicQuery(request.exchange());
|
PostPublicQuery query = new PostPublicQuery(request.exchange());
|
||||||
return postPublicQueryService.list(query.getPage(), query.getSize(), query.toPredicate(),
|
return postPublicQueryService.list(query.toListOptions(), query.toPageRequest())
|
||||||
query.toComparator())
|
|
||||||
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
|
.flatMap(result -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
|
||||||
.bodyValue(result)
|
.bodyValue(result)
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,10 +2,8 @@ package run.halo.app.theme.endpoint;
|
||||||
|
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
import static run.halo.app.theme.endpoint.PublicApiUtils.containsElement;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
@ -15,11 +13,12 @@ import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||||
import run.halo.app.extension.GroupVersion;
|
import run.halo.app.extension.GroupVersion;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
import run.halo.app.extension.router.QueryParamBuildUtil;
|
import run.halo.app.extension.router.QueryParamBuildUtil;
|
||||||
import run.halo.app.extension.router.SortableRequest;
|
import run.halo.app.extension.router.SortableRequest;
|
||||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||||
|
@ -37,6 +36,7 @@ import run.halo.app.theme.finders.vo.TagVo;
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TagQueryEndpoint implements CustomEndpoint {
|
public class TagQueryEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
|
private final ReactiveExtensionClient client;
|
||||||
private final TagFinder tagFinder;
|
private final TagFinder tagFinder;
|
||||||
private final PostPublicQueryService postPublicQueryService;
|
private final PostPublicQueryService postPublicQueryService;
|
||||||
|
|
||||||
|
@ -102,13 +102,11 @@ public class TagQueryEndpoint implements CustomEndpoint {
|
||||||
private Mono<ServerResponse> listPostsByTagName(ServerRequest request) {
|
private Mono<ServerResponse> listPostsByTagName(ServerRequest request) {
|
||||||
final var name = request.pathVariable("name");
|
final var name = request.pathVariable("name");
|
||||||
final var query = new PostPublicQuery(request.exchange());
|
final var query = new PostPublicQuery(request.exchange());
|
||||||
final Predicate<Post> containsTagPredicate =
|
var listOptions = query.toListOptions();
|
||||||
post -> containsElement(post.getSpec().getTags(), name);
|
var newFieldSelector = listOptions.getFieldSelector()
|
||||||
return postPublicQueryService.list(query.getPage(),
|
.andQuery(QueryFactory.equal("spec.tags", name));
|
||||||
query.getSize(),
|
listOptions.setFieldSelector(newFieldSelector);
|
||||||
containsTagPredicate.and(query.toPredicate()),
|
return postPublicQueryService.list(listOptions, query.toPageRequest())
|
||||||
query.toComparator()
|
|
||||||
)
|
|
||||||
.flatMap(result -> ServerResponse.ok()
|
.flatMap(result -> ServerResponse.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.bodyValue(result)
|
.bodyValue(result)
|
||||||
|
@ -117,11 +115,12 @@ public class TagQueryEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
private Mono<ServerResponse> listTags(ServerRequest request) {
|
private Mono<ServerResponse> listTags(ServerRequest request) {
|
||||||
var query = new TagPublicQuery(request.exchange());
|
var query = new TagPublicQuery(request.exchange());
|
||||||
return tagFinder.list(query.getPage(),
|
return client.listBy(Tag.class, query.toListOptions(), query.toPageRequest())
|
||||||
query.getSize(),
|
.map(result -> {
|
||||||
query.toPredicate(),
|
var tagVos = tagFinder.convertToVo(result.getItems());
|
||||||
query.toComparator()
|
return new ListResult<>(result.getPage(), result.getSize(),
|
||||||
)
|
result.getTotal(), tagVos);
|
||||||
|
})
|
||||||
.flatMap(result -> ServerResponse.ok()
|
.flatMap(result -> ServerResponse.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.bodyValue(result)
|
.bodyValue(result)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package run.halo.app.theme.finders;
|
package run.halo.app.theme.finders;
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.theme.ReactivePostContentHandler;
|
import run.halo.app.theme.ReactivePostContentHandler;
|
||||||
import run.halo.app.theme.finders.vo.ContentVo;
|
import run.halo.app.theme.finders.vo.ContentVo;
|
||||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
|
@ -14,17 +14,13 @@ import run.halo.app.theme.finders.vo.PostVo;
|
||||||
public interface PostPublicQueryService {
|
public interface PostPublicQueryService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists posts page by predicate and comparator.
|
* Lists public posts by the given list options and page request.
|
||||||
*
|
*
|
||||||
* @param page page number
|
* @param listOptions additional list options
|
||||||
* @param size page size
|
* @param page page request must not be null
|
||||||
* @param postPredicate post predicate
|
* @return a list of listed post vo
|
||||||
* @param comparator post comparator
|
|
||||||
* @return list result
|
|
||||||
*/
|
*/
|
||||||
Mono<ListResult<ListedPostVo>> list(Integer page, Integer size,
|
Mono<ListResult<ListedPostVo>> list(ListOptions listOptions, PageRequest page);
|
||||||
Predicate<Post> postPredicate,
|
|
||||||
Comparator<Post> comparator);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts post to listed post vo.
|
* Converts post to listed post vo.
|
||||||
|
|
|
@ -24,8 +24,11 @@ public interface TagFinder {
|
||||||
|
|
||||||
Mono<ListResult<TagVo>> list(@Nullable Integer page, @Nullable Integer size);
|
Mono<ListResult<TagVo>> list(@Nullable Integer page, @Nullable Integer size);
|
||||||
|
|
||||||
|
@Deprecated(since = "2.12.0")
|
||||||
Mono<ListResult<TagVo>> list(@Nullable Integer page, @Nullable Integer size,
|
Mono<ListResult<TagVo>> list(@Nullable Integer page, @Nullable Integer size,
|
||||||
@Nullable Predicate<Tag> predicate, @Nullable Comparator<Tag> comparator);
|
@Nullable Predicate<Tag> predicate, @Nullable Comparator<Tag> comparator);
|
||||||
|
|
||||||
|
List<TagVo> convertToVo(List<Tag> tags);
|
||||||
|
|
||||||
Flux<TagVo> listAll();
|
Flux<TagVo> listAll();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,16 @@ import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
import run.halo.app.theme.finders.CategoryFinder;
|
import run.halo.app.theme.finders.CategoryFinder;
|
||||||
import run.halo.app.theme.finders.Finder;
|
import run.halo.app.theme.finders.Finder;
|
||||||
import run.halo.app.theme.finders.vo.CategoryTreeVo;
|
import run.halo.app.theme.finders.vo.CategoryTreeVo;
|
||||||
|
@ -51,10 +56,17 @@ public class CategoryFinderImpl implements CategoryFinder {
|
||||||
.flatMap(this::getByName);
|
.flatMap(this::getByName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Sort defaultSort() {
|
||||||
|
return Sort.by(Sort.Order.desc("spec.priority"),
|
||||||
|
Sort.Order.desc("metadata.creationTimestamp"),
|
||||||
|
Sort.Order.desc("metadata.name"));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<CategoryVo>> list(Integer page, Integer size) {
|
public Mono<ListResult<CategoryVo>> list(Integer page, Integer size) {
|
||||||
return client.list(Category.class, null,
|
return client.listBy(Category.class, new ListOptions(),
|
||||||
defaultComparator(), pageNullSafe(page), sizeNullSafe(size))
|
PageRequestImpl.of(pageNullSafe(page), sizeNullSafe(size), defaultSort())
|
||||||
|
)
|
||||||
.map(list -> {
|
.map(list -> {
|
||||||
List<CategoryVo> categoryVos = list.get()
|
List<CategoryVo> categoryVos = list.get()
|
||||||
.map(CategoryVo::from)
|
.map(CategoryVo::from)
|
||||||
|
@ -65,12 +77,6 @@ public class CategoryFinderImpl implements CategoryFinder {
|
||||||
.defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
|
.defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flux<CategoryVo> listAll() {
|
|
||||||
return client.list(Category.class, null, defaultComparator())
|
|
||||||
.map(CategoryVo::from);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<CategoryTreeVo> listAsTree() {
|
public Flux<CategoryTreeVo> listAsTree() {
|
||||||
return this.toCategoryTreeVoFlux(null);
|
return this.toCategoryTreeVoFlux(null);
|
||||||
|
@ -82,20 +88,9 @@ public class CategoryFinderImpl implements CategoryFinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<CategoryVo> getParentByName(String name) {
|
public Flux<CategoryVo> listAll() {
|
||||||
if (StringUtils.isBlank(name)) {
|
return client.listAll(Category.class, new ListOptions(), defaultSort())
|
||||||
return Mono.empty();
|
.map(CategoryVo::from);
|
||||||
}
|
|
||||||
return client.list(Category.class,
|
|
||||||
category -> {
|
|
||||||
List<String> children = category.getSpec().getChildren();
|
|
||||||
if (children == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return children.contains(name);
|
|
||||||
},
|
|
||||||
defaultComparator())
|
|
||||||
.next().map(CategoryVo::from);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Flux<CategoryTreeVo> toCategoryTreeVoFlux(String name) {
|
Flux<CategoryTreeVo> toCategoryTreeVoFlux(String name) {
|
||||||
|
@ -169,6 +164,22 @@ public class CategoryFinderImpl implements CategoryFinder {
|
||||||
.reversed();
|
.reversed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<CategoryVo> getParentByName(String name) {
|
||||||
|
if (StringUtils.isBlank(name)) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
var listOptions = new ListOptions();
|
||||||
|
listOptions.setFieldSelector(FieldSelector.of(
|
||||||
|
QueryFactory.equal("spec.children", name)
|
||||||
|
));
|
||||||
|
return client.listBy(Category.class, listOptions,
|
||||||
|
PageRequestImpl.of(1, 1, defaultSort())
|
||||||
|
)
|
||||||
|
.map(ListResult::first)
|
||||||
|
.mapNotNull(item -> item.map(CategoryVo::from).orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
int pageNullSafe(Integer page) {
|
int pageNullSafe(Integer page) {
|
||||||
return ObjectUtils.defaultIfNull(page, 1);
|
return ObjectUtils.defaultIfNull(page, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
package run.halo.app.theme.finders.impl;
|
package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.util.comparator.Comparators;
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
|
import run.halo.app.extension.router.selector.LabelSelector;
|
||||||
import run.halo.app.infra.utils.HaloUtils;
|
import run.halo.app.infra.utils.HaloUtils;
|
||||||
import run.halo.app.theme.finders.Finder;
|
import run.halo.app.theme.finders.Finder;
|
||||||
import run.halo.app.theme.finders.PostFinder;
|
import run.halo.app.theme.finders.PostFinder;
|
||||||
|
@ -64,33 +65,32 @@ public class PostFinderImpl implements PostFinder {
|
||||||
return postPublicQueryService.getContent(postName);
|
return postPublicQueryService.getContent(postName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
static Sort defaultSort() {
|
||||||
public Mono<NavigationPostVo> cursor(String currentName) {
|
return Sort.by(Sort.Order.desc("spec.pinned"),
|
||||||
// TODO Optimize the post names query here
|
Sort.Order.desc("spec.priority"),
|
||||||
return postPredicateResolver.getPredicate()
|
Sort.Order.desc("spec.publishTime"),
|
||||||
.flatMapMany(postPredicate ->
|
Sort.Order.desc("metadata.name")
|
||||||
client.list(Post.class, postPredicate, defaultComparator())
|
);
|
||||||
)
|
}
|
||||||
.map(post -> post.getMetadata().getName())
|
|
||||||
.collectList()
|
@NonNull
|
||||||
.flatMap(postNames -> Mono.just(NavigationPostVo.builder())
|
static LinkNavigation findPostNavigation(List<String> postNames, String target) {
|
||||||
.flatMap(builder -> getByName(currentName)
|
Assert.notNull(target, "Target post name must not be null");
|
||||||
.doOnNext(builder::current)
|
for (int i = 0; i < postNames.size(); i++) {
|
||||||
.thenReturn(builder)
|
var item = postNames.get(i);
|
||||||
)
|
if (target.equals(item)) {
|
||||||
.flatMap(builder -> {
|
var prevLink = (i > 0) ? postNames.get(i - 1) : null;
|
||||||
Pair<String, String> previousNextPair =
|
var nextLink = (i < postNames.size() - 1) ? postNames.get(i + 1) : null;
|
||||||
postPreviousNextPair(postNames, currentName);
|
return new LinkNavigation(prevLink, target, nextLink);
|
||||||
String previousPostName = previousNextPair.getLeft();
|
}
|
||||||
String nextPostName = previousNextPair.getRight();
|
}
|
||||||
return fetchByName(previousPostName)
|
return new LinkNavigation(null, target, null);
|
||||||
.doOnNext(builder::previous)
|
}
|
||||||
.then(fetchByName(nextPostName))
|
|
||||||
.doOnNext(builder::next)
|
static Sort archiveSort() {
|
||||||
.thenReturn(builder);
|
return Sort.by(Sort.Order.desc("spec.publishTime"),
|
||||||
})
|
Sort.Order.desc("metadata.name")
|
||||||
.map(NavigationPostVo.NavigationPostVoBuilder::build))
|
);
|
||||||
.defaultIfEmpty(NavigationPostVo.empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<PostVo> fetchByName(String name) {
|
private Mono<PostVo> fetchByName(String name) {
|
||||||
|
@ -102,106 +102,76 @@ public class PostFinderImpl implements PostFinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<ListedPostVo> listAll() {
|
public Mono<NavigationPostVo> cursor(String currentName) {
|
||||||
return postPredicateResolver.getPredicate()
|
return postPredicateResolver.getListOptions()
|
||||||
.flatMapMany(predicate -> client.list(Post.class, predicate, defaultComparator()))
|
.flatMapMany(postListOption ->
|
||||||
.concatMap(postPublicQueryService::convertToListedVo);
|
client.listAll(Post.class, postListOption, defaultSort())
|
||||||
}
|
)
|
||||||
|
.map(post -> post.getMetadata().getName())
|
||||||
static Pair<String, String> postPreviousNextPair(List<String> postNames,
|
.collectList()
|
||||||
String currentName) {
|
.flatMap(postNames -> Mono.just(NavigationPostVo.builder())
|
||||||
FixedSizeSlidingWindow<String> window = new FixedSizeSlidingWindow<>(3);
|
.flatMap(builder -> getByName(currentName)
|
||||||
for (String postName : postNames) {
|
.doOnNext(builder::current)
|
||||||
window.add(postName);
|
.thenReturn(builder)
|
||||||
if (!window.isFull()) {
|
)
|
||||||
continue;
|
.flatMap(builder -> {
|
||||||
}
|
var previousNextPair = findPostNavigation(postNames, currentName);
|
||||||
int index = window.indexOf(currentName);
|
String previousPostName = previousNextPair.prev();
|
||||||
if (index == -1) {
|
String nextPostName = previousNextPair.next();
|
||||||
continue;
|
return fetchByName(previousPostName)
|
||||||
}
|
.doOnNext(builder::previous)
|
||||||
// got expected window
|
.then(fetchByName(nextPostName))
|
||||||
if (index < 2) {
|
.doOnNext(builder::next)
|
||||||
break;
|
.thenReturn(builder);
|
||||||
}
|
})
|
||||||
}
|
.map(NavigationPostVo.NavigationPostVoBuilder::build))
|
||||||
|
.defaultIfEmpty(NavigationPostVo.empty());
|
||||||
List<String> elements = window.elements();
|
|
||||||
// current post index
|
|
||||||
int index = elements.indexOf(currentName);
|
|
||||||
|
|
||||||
String previousPostName = null;
|
|
||||||
if (index > 0) {
|
|
||||||
previousPostName = elements.get(index - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String nextPostName = null;
|
|
||||||
if (elements.size() - 1 > index) {
|
|
||||||
nextPostName = elements.get(index + 1);
|
|
||||||
}
|
|
||||||
return Pair.of(previousPostName, nextPostName);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class FixedSizeSlidingWindow<T> {
|
|
||||||
Deque<T> queue;
|
|
||||||
int size;
|
|
||||||
|
|
||||||
public FixedSizeSlidingWindow(int size) {
|
|
||||||
this.size = size;
|
|
||||||
// FIFO
|
|
||||||
queue = new ArrayDeque<>(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add element to the window.
|
|
||||||
* The element added first will be deleted when the element in the collection exceeds
|
|
||||||
* {@code size}.
|
|
||||||
*/
|
|
||||||
public void add(T t) {
|
|
||||||
if (queue.size() == size) {
|
|
||||||
// remove first
|
|
||||||
queue.poll();
|
|
||||||
}
|
|
||||||
// add to last
|
|
||||||
queue.add(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int indexOf(T o) {
|
|
||||||
List<T> elements = elements();
|
|
||||||
return elements.indexOf(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<T> elements() {
|
|
||||||
return new ArrayList<>(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFull() {
|
|
||||||
return queue.size() == size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPostVo>> list(Integer page, Integer size) {
|
public Mono<ListResult<ListedPostVo>> list(Integer page, Integer size) {
|
||||||
return postPublicQueryService.list(page, size, null, defaultComparator());
|
return postPublicQueryService.list(new ListOptions(), getPageRequest(page, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PageRequestImpl getPageRequest(Integer page, Integer size) {
|
||||||
|
return PageRequestImpl.of(pageNullSafe(page), sizeNullSafe(size), defaultSort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPostVo>> listByCategory(Integer page, Integer size,
|
public Mono<ListResult<ListedPostVo>> listByCategory(Integer page, Integer size,
|
||||||
String categoryName) {
|
String categoryName) {
|
||||||
return postPublicQueryService.list(page, size,
|
var fieldQuery = QueryFactory.all();
|
||||||
post -> contains(post.getSpec().getCategories(), categoryName), defaultComparator());
|
if (StringUtils.isNotBlank(categoryName)) {
|
||||||
|
fieldQuery =
|
||||||
|
QueryFactory.and(fieldQuery, QueryFactory.equal("spec.categories", categoryName));
|
||||||
|
}
|
||||||
|
var listOptions = new ListOptions();
|
||||||
|
listOptions.setFieldSelector(FieldSelector.of(fieldQuery));
|
||||||
|
return postPublicQueryService.list(listOptions, getPageRequest(page, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPostVo>> listByTag(Integer page, Integer size, String tag) {
|
public Mono<ListResult<ListedPostVo>> listByTag(Integer page, Integer size, String tag) {
|
||||||
return postPublicQueryService.list(page, size,
|
var fieldQuery = QueryFactory.all();
|
||||||
post -> contains(post.getSpec().getTags(), tag), defaultComparator());
|
if (StringUtils.isNotBlank(tag)) {
|
||||||
|
fieldQuery =
|
||||||
|
QueryFactory.and(fieldQuery, QueryFactory.equal("spec.tags", tag));
|
||||||
|
}
|
||||||
|
var listOptions = new ListOptions();
|
||||||
|
listOptions.setFieldSelector(FieldSelector.of(fieldQuery));
|
||||||
|
return postPublicQueryService.list(listOptions, getPageRequest(page, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPostVo>> listByOwner(Integer page, Integer size, String owner) {
|
public Mono<ListResult<ListedPostVo>> listByOwner(Integer page, Integer size, String owner) {
|
||||||
return postPublicQueryService.list(page, size,
|
var fieldQuery = QueryFactory.all();
|
||||||
post -> post.getSpec().getOwner().equals(owner), defaultComparator());
|
if (StringUtils.isNotBlank(owner)) {
|
||||||
|
fieldQuery =
|
||||||
|
QueryFactory.and(fieldQuery, QueryFactory.equal("spec.owner", owner));
|
||||||
|
}
|
||||||
|
var listOptions = new ListOptions();
|
||||||
|
listOptions.setFieldSelector(FieldSelector.of(fieldQuery));
|
||||||
|
return postPublicQueryService.list(listOptions, getPageRequest(page, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -217,23 +187,23 @@ public class PostFinderImpl implements PostFinder {
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size, String year,
|
public Mono<ListResult<PostArchiveVo>> archives(Integer page, Integer size, String year,
|
||||||
String month) {
|
String month) {
|
||||||
return postPublicQueryService.list(page, size, post -> {
|
var listOptions = new ListOptions();
|
||||||
Map<String, String> labels = post.getMetadata().getLabels();
|
var labelSelectorBuilder = LabelSelector.builder();
|
||||||
if (labels == null) {
|
if (StringUtils.isNotBlank(year)) {
|
||||||
return false;
|
labelSelectorBuilder.eq(Post.ARCHIVE_YEAR_LABEL, year);
|
||||||
}
|
}
|
||||||
boolean yearMatch = StringUtils.isBlank(year)
|
if (StringUtils.isNotBlank(month)) {
|
||||||
|| year.equals(labels.get(Post.ARCHIVE_YEAR_LABEL));
|
labelSelectorBuilder.eq(Post.ARCHIVE_MONTH_LABEL, month);
|
||||||
boolean monthMatch = StringUtils.isBlank(month)
|
}
|
||||||
|| month.equals(labels.get(Post.ARCHIVE_MONTH_LABEL));
|
listOptions.setLabelSelector(labelSelectorBuilder.build());
|
||||||
return yearMatch && monthMatch;
|
var pageRequest = PageRequestImpl.of(pageNullSafe(page), sizeNullSafe(size), archiveSort());
|
||||||
}, archiveComparator())
|
return postPublicQueryService.list(listOptions, pageRequest)
|
||||||
.map(list -> {
|
.map(list -> {
|
||||||
Map<String, List<ListedPostVo>> yearPosts = list.get()
|
Map<String, List<ListedPostVo>> yearPosts = list.get()
|
||||||
.collect(Collectors.groupingBy(
|
.collect(Collectors.groupingBy(
|
||||||
post -> HaloUtils.getYearText(post.getSpec().getPublishTime())));
|
post -> HaloUtils.getYearText(post.getSpec().getPublishTime())));
|
||||||
List<PostArchiveVo> postArchives =
|
List<PostArchiveVo> postArchives = yearPosts.entrySet().stream()
|
||||||
yearPosts.entrySet().stream().map(entry -> {
|
.map(entry -> {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
// archives by month
|
// archives by month
|
||||||
Map<String, List<ListedPostVo>> monthPosts = entry.getValue().stream()
|
Map<String, List<ListedPostVo>> monthPosts = entry.getValue().stream()
|
||||||
|
@ -260,37 +230,24 @@ public class PostFinderImpl implements PostFinder {
|
||||||
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
|
return new ListResult<>(list.getPage(), list.getSize(), list.getTotal(),
|
||||||
postArchives);
|
postArchives);
|
||||||
})
|
})
|
||||||
.defaultIfEmpty(new ListResult<>(page, size, 0, List.of()));
|
.defaultIfEmpty(ListResult.emptyResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean contains(List<String> c, String key) {
|
@Override
|
||||||
if (StringUtils.isBlank(key) || c == null) {
|
public Flux<ListedPostVo> listAll() {
|
||||||
return false;
|
return postPredicateResolver.getListOptions()
|
||||||
}
|
.flatMapMany(listOptions -> client.listAll(Post.class, listOptions, defaultSort()))
|
||||||
return c.contains(key);
|
.concatMap(postPublicQueryService::convertToListedVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Comparator<Post> defaultComparator() {
|
int pageNullSafe(Integer page) {
|
||||||
Function<Post, Boolean> pinned =
|
return ObjectUtils.defaultIfNull(page, 1);
|
||||||
post -> Objects.requireNonNullElse(post.getSpec().getPinned(), false);
|
|
||||||
Function<Post, Integer> priority =
|
|
||||||
post -> Objects.requireNonNullElse(post.getSpec().getPriority(), 0);
|
|
||||||
Function<Post, Instant> publishTime =
|
|
||||||
post -> post.getSpec().getPublishTime();
|
|
||||||
Function<Post, String> name = post -> post.getMetadata().getName();
|
|
||||||
return Comparator.comparing(pinned)
|
|
||||||
.thenComparing(priority)
|
|
||||||
.thenComparing(publishTime, Comparators.nullsLow())
|
|
||||||
.thenComparing(name)
|
|
||||||
.reversed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Comparator<Post> archiveComparator() {
|
int sizeNullSafe(Integer size) {
|
||||||
Function<Post, Instant> publishTime =
|
return ObjectUtils.defaultIfNull(size, 10);
|
||||||
post -> post.getSpec().getPublishTime();
|
}
|
||||||
Function<Post, String> name = post -> post.getMetadata().getName();
|
|
||||||
return Comparator.comparing(publishTime, Comparators.nullsLow())
|
record LinkNavigation(String prev, String current, String next) {
|
||||||
.thenComparing(name)
|
|
||||||
.reversed();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
package run.halo.app.theme.finders.impl;
|
package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -15,7 +12,9 @@ import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.content.ContentWrapper;
|
import run.halo.app.content.ContentWrapper;
|
||||||
import run.halo.app.content.PostService;
|
import run.halo.app.content.PostService;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.metrics.CounterService;
|
import run.halo.app.metrics.CounterService;
|
||||||
import run.halo.app.metrics.MeterUtils;
|
import run.halo.app.metrics.MeterUtils;
|
||||||
|
@ -52,13 +51,21 @@ public class PostPublicQueryServiceImpl implements PostPublicQueryService {
|
||||||
private final ReactiveQueryPostPredicateResolver postPredicateResolver;
|
private final ReactiveQueryPostPredicateResolver postPredicateResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPostVo>> list(Integer page, Integer size,
|
public Mono<ListResult<ListedPostVo>> list(ListOptions queryOptions, PageRequest page) {
|
||||||
Predicate<Post> postPredicate, Comparator<Post> comparator) {
|
return postPredicateResolver.getListOptions()
|
||||||
return postPredicateResolver.getPredicate()
|
.map(option -> {
|
||||||
.map(predicate -> predicate.and(postPredicate == null ? post -> true : postPredicate))
|
var fieldSelector = queryOptions.getFieldSelector();
|
||||||
.flatMap(predicate -> client.list(Post.class, predicate,
|
if (fieldSelector != null) {
|
||||||
comparator, pageNullSafe(page), sizeNullSafe(size))
|
option.setFieldSelector(option.getFieldSelector()
|
||||||
)
|
.andQuery(fieldSelector.query()));
|
||||||
|
}
|
||||||
|
var labelSelector = queryOptions.getLabelSelector();
|
||||||
|
if (labelSelector != null) {
|
||||||
|
option.setLabelSelector(option.getLabelSelector().and(labelSelector));
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
})
|
||||||
|
.flatMap(listOptions -> client.listBy(Post.class, listOptions, page))
|
||||||
.flatMap(list -> Flux.fromStream(list.get())
|
.flatMap(list -> Flux.fromStream(list.get())
|
||||||
.concatMap(post -> convertToListedVo(post)
|
.concatMap(post -> convertToListedVo(post)
|
||||||
.flatMap(postVo -> populateStats(postVo)
|
.flatMap(postVo -> populateStats(postVo)
|
||||||
|
@ -70,9 +77,10 @@ public class PostPublicQueryServiceImpl implements PostPublicQueryService {
|
||||||
postVos)
|
postVos)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.defaultIfEmpty(new ListResult<>(page, size, 0L, List.of()));
|
.defaultIfEmpty(ListResult.emptyResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListedPostVo> convertToListedVo(@NonNull Post post) {
|
public Mono<ListedPostVo> convertToListedVo(@NonNull Post post) {
|
||||||
Assert.notNull(post, "Post must not be null");
|
Assert.notNull(post, "Post must not be null");
|
||||||
|
@ -180,12 +188,4 @@ public class PostPublicQueryServiceImpl implements PostPublicQueryService {
|
||||||
)
|
)
|
||||||
.defaultIfEmpty(StatsVo.empty());
|
.defaultIfEmpty(StatsVo.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
int pageNullSafe(Integer page) {
|
|
||||||
return ObjectUtils.defaultIfNull(page, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sizeNullSafe(Integer size) {
|
|
||||||
return ObjectUtils.defaultIfNull(size, 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
package run.halo.app.theme.finders.impl;
|
package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
|
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 lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.Counter;
|
import run.halo.app.core.extension.Counter;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
|
import run.halo.app.extension.router.selector.LabelSelector;
|
||||||
import run.halo.app.theme.finders.Finder;
|
import run.halo.app.theme.finders.Finder;
|
||||||
import run.halo.app.theme.finders.SiteStatsFinder;
|
import run.halo.app.theme.finders.SiteStatsFinder;
|
||||||
import run.halo.app.theme.finders.vo.SiteStatsVo;
|
import run.halo.app.theme.finders.vo.SiteStatsVo;
|
||||||
|
@ -40,14 +49,22 @@ public class SiteStatsFinderImpl implements SiteStatsFinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
Mono<Integer> postCount() {
|
Mono<Integer> postCount() {
|
||||||
return client.list(Post.class, post -> !post.isDeleted() && post.isPublished(), null)
|
var listOptions = new ListOptions();
|
||||||
.count()
|
listOptions.setLabelSelector(LabelSelector.builder()
|
||||||
.map(Long::intValue);
|
.eq(Post.PUBLISHED_LABEL, "true")
|
||||||
|
.build());
|
||||||
|
var fieldQuery = and(
|
||||||
|
isNull("metadata.deletionTimestamp"),
|
||||||
|
equal("spec.deleted", "false")
|
||||||
|
);
|
||||||
|
listOptions.setFieldSelector(FieldSelector.of(fieldQuery));
|
||||||
|
return client.listBy(Post.class, listOptions, PageRequestImpl.ofSize(1))
|
||||||
|
.map(result -> (int) result.getTotal());
|
||||||
}
|
}
|
||||||
|
|
||||||
Mono<Integer> categoryCount() {
|
Mono<Integer> categoryCount() {
|
||||||
return client.list(Category.class, null, null)
|
return client.listBy(Category.class, new ListOptions(), PageRequestImpl.ofSize(1))
|
||||||
.count()
|
.map(ListResult::getTotal)
|
||||||
.map(Long::intValue);
|
.map(Long::intValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,16 @@ import java.util.Optional;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.theme.finders.Finder;
|
import run.halo.app.theme.finders.Finder;
|
||||||
import run.halo.app.theme.finders.TagFinder;
|
import run.halo.app.theme.finders.TagFinder;
|
||||||
|
@ -48,7 +53,8 @@ public class TagFinderImpl implements TagFinder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<TagVo>> list(Integer page, Integer size) {
|
public Mono<ListResult<TagVo>> list(Integer page, Integer size) {
|
||||||
return list(page, size, null, null);
|
return listBy(new ListOptions(),
|
||||||
|
PageRequestImpl.of(pageNullSafe(page), sizeNullSafe(size)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -68,13 +74,34 @@ public class TagFinderImpl implements TagFinder {
|
||||||
new ListResult<>(pageNullSafe(page), sizeNullSafe(size), 0L, List.of()));
|
new ListResult<>(pageNullSafe(page), sizeNullSafe(size), 0L, List.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TagVo> convertToVo(List<Tag> tags) {
|
||||||
|
if (CollectionUtils.isEmpty(tags)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return tags.stream()
|
||||||
|
.map(TagVo::from)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<TagVo> listAll() {
|
public Flux<TagVo> listAll() {
|
||||||
return client.list(Tag.class, null,
|
return client.listAll(Tag.class, new ListOptions(),
|
||||||
DEFAULT_COMPARATOR.reversed())
|
Sort.by(Sort.Order.desc("metadata.creationTimestamp")))
|
||||||
.map(TagVo::from);
|
.map(TagVo::from);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<ListResult<TagVo>> listBy(ListOptions listOptions, PageRequest pageRequest) {
|
||||||
|
return client.listBy(Tag.class, listOptions, pageRequest)
|
||||||
|
.map(result -> {
|
||||||
|
List<TagVo> tagVos = result.get()
|
||||||
|
.map(TagVo::from)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new ListResult<>(result.getPage(), result.getSize(), result.getTotal(),
|
||||||
|
tagVos);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
int pageNullSafe(Integer page) {
|
int pageNullSafe(Integer page) {
|
||||||
return ObjectUtils.defaultIfNull(page, 1);
|
return ObjectUtils.defaultIfNull(page, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package run.halo.app.theme.router;
|
package run.halo.app.theme.router;
|
||||||
|
|
||||||
|
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 static run.halo.app.extension.index.query.QueryFactory.or;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
@ -9,6 +14,9 @@ import org.springframework.stereotype.Component;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.extension.ExtensionUtil;
|
import run.halo.app.extension.ExtensionUtil;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
|
import run.halo.app.extension.router.selector.LabelSelector;
|
||||||
import run.halo.app.infra.AnonymousUserConst;
|
import run.halo.app.infra.AnonymousUserConst;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,6 +42,28 @@ public class DefaultQueryPostPredicateResolver implements ReactiveQueryPostPredi
|
||||||
.defaultIfEmpty(predicate.and(visiblePredicate));
|
.defaultIfEmpty(predicate.and(visiblePredicate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ListOptions> getListOptions() {
|
||||||
|
var listOptions = new ListOptions();
|
||||||
|
listOptions.setLabelSelector(LabelSelector.builder()
|
||||||
|
.eq(Post.PUBLISHED_LABEL, "true").build());
|
||||||
|
|
||||||
|
var fieldQuery = and(
|
||||||
|
isNull("metadata.deletionTimestamp"),
|
||||||
|
equal("spec.deleted", "false")
|
||||||
|
);
|
||||||
|
var visibleQuery = equal("spec.visible", Post.VisibleEnum.PUBLIC.name());
|
||||||
|
return currentUserName()
|
||||||
|
.map(username -> and(fieldQuery,
|
||||||
|
or(visibleQuery, equal("spec.owner", username)))
|
||||||
|
)
|
||||||
|
.defaultIfEmpty(and(fieldQuery, visibleQuery))
|
||||||
|
.map(query -> {
|
||||||
|
listOptions.setFieldSelector(FieldSelector.of(query));
|
||||||
|
return listOptions;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Mono<String> currentUserName() {
|
Mono<String> currentUserName() {
|
||||||
return ReactiveSecurityContextHolder.getContext()
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
.map(SecurityContext::getAuthentication)
|
.map(SecurityContext::getAuthentication)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
|
@ -14,6 +15,7 @@ import run.halo.app.core.extension.content.Tag;
|
||||||
import run.halo.app.extension.AbstractExtension;
|
import run.halo.app.extension.AbstractExtension;
|
||||||
import run.halo.app.extension.Extension;
|
import run.halo.app.extension.Extension;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.MetadataOperator;
|
import run.halo.app.extension.MetadataOperator;
|
||||||
import run.halo.app.extension.MetadataUtil;
|
import run.halo.app.extension.MetadataUtil;
|
||||||
import run.halo.app.theme.DefaultTemplateEnum;
|
import run.halo.app.theme.DefaultTemplateEnum;
|
||||||
|
@ -52,7 +54,7 @@ public class ExtensionPermalinkPatternUpdater
|
||||||
|
|
||||||
private void updatePostPermalink(String pattern) {
|
private void updatePostPermalink(String pattern) {
|
||||||
log.debug("Update post permalink by new policy [{}]", pattern);
|
log.debug("Update post permalink by new policy [{}]", pattern);
|
||||||
client.list(Post.class, null, null)
|
client.listAll(Post.class, new ListOptions(), Sort.unsorted())
|
||||||
.forEach(post -> updateIfPermalinkPatternChanged(post, pattern));
|
.forEach(post -> updateIfPermalinkPatternChanged(post, pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,13 +72,13 @@ public class ExtensionPermalinkPatternUpdater
|
||||||
|
|
||||||
private void updateCategoryPermalink(String pattern) {
|
private void updateCategoryPermalink(String pattern) {
|
||||||
log.debug("Update category and categories permalink by new policy [{}]", pattern);
|
log.debug("Update category and categories permalink by new policy [{}]", pattern);
|
||||||
client.list(Category.class, null, null)
|
client.listAll(Category.class, new ListOptions(), Sort.unsorted())
|
||||||
.forEach(category -> updateIfPermalinkPatternChanged(category, pattern));
|
.forEach(category -> updateIfPermalinkPatternChanged(category, pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTagPermalink(String pattern) {
|
private void updateTagPermalink(String pattern) {
|
||||||
log.debug("Update tag and tags permalink by new policy [{}]", pattern);
|
log.debug("Update tag and tags permalink by new policy [{}]", pattern);
|
||||||
client.list(Tag.class, null, null)
|
client.listAll(Tag.class, new ListOptions(), Sort.unsorted())
|
||||||
.forEach(tag -> updateIfPermalinkPatternChanged(tag, pattern));
|
.forEach(tag -> updateIfPermalinkPatternChanged(tag, pattern));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.theme.router;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The reactive query post predicate resolver.
|
* The reactive query post predicate resolver.
|
||||||
|
@ -13,4 +14,6 @@ import run.halo.app.core.extension.content.Post;
|
||||||
public interface ReactiveQueryPostPredicateResolver {
|
public interface ReactiveQueryPostPredicateResolver {
|
||||||
|
|
||||||
Mono<Predicate<Post>> getPredicate();
|
Mono<Predicate<Post>> getPredicate();
|
||||||
|
|
||||||
|
Mono<ListOptions> getListOptions();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,12 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.exception.NotFoundException;
|
import run.halo.app.infra.exception.NotFoundException;
|
||||||
|
@ -81,10 +86,18 @@ public class CategoryPostRouteFactory implements RouteFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
Mono<CategoryVo> fetchBySlug(String slug) {
|
Mono<CategoryVo> fetchBySlug(String slug) {
|
||||||
return client.list(Category.class, category -> category.getSpec().getSlug().equals(slug)
|
var listOptions = new ListOptions();
|
||||||
&& category.getMetadata().getDeletionTimestamp() == null, null)
|
listOptions.setFieldSelector(FieldSelector.of(
|
||||||
.next()
|
QueryFactory.and(
|
||||||
.map(CategoryVo::from);
|
QueryFactory.equal("spec.slug", slug),
|
||||||
|
QueryFactory.isNull("metadata.deletionTimestamp")
|
||||||
|
)
|
||||||
|
));
|
||||||
|
return client.listBy(Category.class, listOptions, PageRequestImpl.ofSize(1))
|
||||||
|
.mapNotNull(result -> ListResult.first(result)
|
||||||
|
.map(CategoryVo::from)
|
||||||
|
.orElse(null)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<UrlContextListResult<ListedPostVo>> postListByCategoryName(String name,
|
private Mono<UrlContextListResult<ListedPostVo>> postListByCategoryName(String name,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -32,6 +33,7 @@ import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.extension.MetadataUtil;
|
import run.halo.app.extension.MetadataUtil;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
import run.halo.app.infra.exception.NotFoundException;
|
import run.halo.app.infra.exception.NotFoundException;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
import run.halo.app.theme.DefaultTemplateEnum;
|
import run.halo.app.theme.DefaultTemplateEnum;
|
||||||
|
@ -159,11 +161,14 @@ public class PostRouteFactory implements RouteFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<Post> fetchPostsBySlug(String slug) {
|
private Flux<Post> fetchPostsBySlug(String slug) {
|
||||||
return queryPostPredicateResolver.getPredicate()
|
return queryPostPredicateResolver.getListOptions()
|
||||||
.flatMapMany(predicate -> client.list(Post.class,
|
.flatMapMany(listOptions -> {
|
||||||
predicate.and(post -> matchIfPresent(slug, post.getSpec().getSlug())),
|
if (StringUtils.isNotBlank(slug)) {
|
||||||
null)
|
var other = QueryFactory.equal("spec.slug", slug);
|
||||||
);
|
listOptions.setFieldSelector(listOptions.getFieldSelector().andQuery(other));
|
||||||
|
}
|
||||||
|
return client.listAll(Post.class, listOptions, Sort.unsorted());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean matchIfPresent(String variable, String target) {
|
private boolean matchIfPresent(String variable, String target) {
|
||||||
|
|
|
@ -15,7 +15,12 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.index.query.QueryFactory;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.exception.NotFoundException;
|
import run.halo.app.infra.exception.NotFoundException;
|
||||||
|
@ -95,9 +100,14 @@ public class TagPostRouteFactory implements RouteFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<TagVo> tagBySlug(String slug) {
|
private Mono<TagVo> tagBySlug(String slug) {
|
||||||
return client.list(Tag.class, tag -> tag.getSpec().getSlug().equals(slug)
|
var listOptions = new ListOptions();
|
||||||
&& tag.getMetadata().getDeletionTimestamp() == null, null)
|
listOptions.setFieldSelector(FieldSelector.of(
|
||||||
.next()
|
QueryFactory.and(QueryFactory.equal("spec.slug", slug),
|
||||||
|
QueryFactory.isNull("metadata.deletionTimestamp")
|
||||||
|
)
|
||||||
|
));
|
||||||
|
return client.listBy(Tag.class, listOptions, PageRequestImpl.ofSize(1))
|
||||||
|
.mapNotNull(result -> ListResult.first(result).orElse(null))
|
||||||
.flatMap(tag -> tagFinder.getByName(tag.getMetadata().getName()))
|
.flatMap(tag -> tagFinder.getByName(tag.getMetadata().getName()))
|
||||||
.switchIfEmpty(
|
.switchIfEmpty(
|
||||||
Mono.error(new NotFoundException("Tag not found with slug: " + slug)));
|
Mono.error(new NotFoundException("Tag not found with slug: " + slug)));
|
||||||
|
|
|
@ -1,368 +0,0 @@
|
||||||
package run.halo.app.content;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.skyscreamer.jsonassert.JSONAssert;
|
|
||||||
import run.halo.app.core.extension.content.Post;
|
|
||||||
import run.halo.app.extension.Metadata;
|
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link DefaultIndexer}.
|
|
||||||
*
|
|
||||||
* @author guqing
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
class DefaultIndexerTest {
|
|
||||||
@Test
|
|
||||||
public void testTagsIndexer() throws JSONException {
|
|
||||||
// Create a new Indexer that indexes Post objects by tags.
|
|
||||||
DefaultIndexer<Post> indexer = new DefaultIndexer<>();
|
|
||||||
String tagsIndexName = "tags";
|
|
||||||
indexer.addIndexFunc(tagsIndexName, post -> {
|
|
||||||
List<String> tags = post.getSpec().getTags();
|
|
||||||
return tags == null ? Set.of() : Set.copyOf(tags);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create some Post objects.
|
|
||||||
Post post1 = new Post();
|
|
||||||
post1.setMetadata(new Metadata());
|
|
||||||
post1.getMetadata().setName("post-1");
|
|
||||||
post1.setSpec(new Post.PostSpec());
|
|
||||||
post1.getSpec().setTags(List.of("t1", "t2"));
|
|
||||||
|
|
||||||
Post post2 = new Post();
|
|
||||||
post2.setMetadata(new Metadata());
|
|
||||||
post2.getMetadata().setName("post-2");
|
|
||||||
post2.setSpec(new Post.PostSpec());
|
|
||||||
post2.getSpec().setTags(List.of("t2", "t3"));
|
|
||||||
|
|
||||||
Post post3 = new Post();
|
|
||||||
post3.setMetadata(new Metadata());
|
|
||||||
post3.getMetadata().setName("post-3");
|
|
||||||
post3.setSpec(new Post.PostSpec());
|
|
||||||
post3.getSpec().setTags(List.of("t3", "t4"));
|
|
||||||
|
|
||||||
// Add the Post objects to the Indexer.
|
|
||||||
indexer.add(tagsIndexName, post1);
|
|
||||||
indexer.add(tagsIndexName, post2);
|
|
||||||
indexer.add(tagsIndexName, post3);
|
|
||||||
|
|
||||||
// Verify that the Indexer has the correct indices.
|
|
||||||
JSONAssert.assertEquals("""
|
|
||||||
{
|
|
||||||
"t4": [
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"t3": [
|
|
||||||
"post-2",
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"t2": [
|
|
||||||
"post-1",
|
|
||||||
"post-2"
|
|
||||||
],
|
|
||||||
"t1": [
|
|
||||||
"post-1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
JsonUtils.objectToJson(indexer.getIndices("tags")),
|
|
||||||
true);
|
|
||||||
|
|
||||||
// Remove post2 from the Indexer.
|
|
||||||
indexer.delete(tagsIndexName, post2);
|
|
||||||
|
|
||||||
// Verify that the Indexer has the correct indices.
|
|
||||||
JSONAssert.assertEquals("""
|
|
||||||
{
|
|
||||||
"t1": [
|
|
||||||
"post-1"
|
|
||||||
],
|
|
||||||
"t2": [
|
|
||||||
"post-1"
|
|
||||||
],
|
|
||||||
"t3": [
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"t4": [
|
|
||||||
"post-3"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
JsonUtils.objectToJson(indexer.getIndices("tags")),
|
|
||||||
true);
|
|
||||||
|
|
||||||
// Update post3 in the Indexer.
|
|
||||||
post3.getSpec().setTags(List.of("t4", "t5"));
|
|
||||||
indexer.update(tagsIndexName, post3);
|
|
||||||
|
|
||||||
// Verify that the Indexer has the correct indices.
|
|
||||||
JSONAssert.assertEquals("""
|
|
||||||
{
|
|
||||||
"t1": [
|
|
||||||
"post-1"
|
|
||||||
],
|
|
||||||
"t2": [
|
|
||||||
"post-1"
|
|
||||||
],
|
|
||||||
"t4": [
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"t5": [
|
|
||||||
"post-3"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
JsonUtils.objectToJson(indexer.getIndices("tags")),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLabelIndexer() throws JSONException {
|
|
||||||
// Create a new Indexer.
|
|
||||||
DefaultIndexer<Post> indexer = new DefaultIndexer<>();
|
|
||||||
|
|
||||||
// Define the IndexFunc for labels.
|
|
||||||
DefaultIndexer.IndexFunc<Post> labelIndexFunc = labelIndexFunc();
|
|
||||||
|
|
||||||
// Add the label IndexFunc to the Indexer.
|
|
||||||
String labelsIndexName = "labels";
|
|
||||||
indexer.addIndexFunc(labelsIndexName, labelIndexFunc);
|
|
||||||
|
|
||||||
// Create some posts with labels.
|
|
||||||
Post post1 = new Post();
|
|
||||||
post1.setMetadata(new Metadata());
|
|
||||||
post1.getMetadata().setName("post-1");
|
|
||||||
post1.getMetadata().setLabels(Map.of("app", "myapp", "env", "prod"));
|
|
||||||
|
|
||||||
Post post2 = new Post();
|
|
||||||
post2.setMetadata(new Metadata());
|
|
||||||
post2.getMetadata().setName("post-2");
|
|
||||||
post2.getMetadata().setLabels(Map.of("app", "myapp", "env", "test"));
|
|
||||||
|
|
||||||
Post post3 = new Post();
|
|
||||||
post3.setMetadata(new Metadata());
|
|
||||||
post3.getMetadata().setName("post-3");
|
|
||||||
post3.getMetadata().setLabels(Map.of("app", "otherapp", "env", "prod"));
|
|
||||||
|
|
||||||
// Add the posts to the Indexer.
|
|
||||||
indexer.add(labelsIndexName, post1);
|
|
||||||
indexer.add(labelsIndexName, post2);
|
|
||||||
indexer.add(labelsIndexName, post3);
|
|
||||||
|
|
||||||
// Verify that the Indexer has the correct indices.
|
|
||||||
assertEquals(
|
|
||||||
Map.of(
|
|
||||||
"app=myapp", Set.of("post-1", "post-2"),
|
|
||||||
"app=otherapp", Set.of("post-3"),
|
|
||||||
"env=test", Set.of("post-2"),
|
|
||||||
"env=prod", Set.of("post-1", "post-3")
|
|
||||||
),
|
|
||||||
indexer.getIndices("labels"));
|
|
||||||
|
|
||||||
// Delete post2 from the Indexer.
|
|
||||||
indexer.delete(labelsIndexName, post2);
|
|
||||||
|
|
||||||
// Verify that the Indexer has the correct indices.
|
|
||||||
JSONAssert.assertEquals("""
|
|
||||||
{
|
|
||||||
"app=myapp": [
|
|
||||||
"post-1"
|
|
||||||
],
|
|
||||||
"env=prod": [
|
|
||||||
"post-1",
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"app=otherapp": [
|
|
||||||
"post-3"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
JsonUtils.objectToJson(indexer.getIndices("labels")),
|
|
||||||
true);
|
|
||||||
|
|
||||||
// Update post2 in the Indexer.
|
|
||||||
post2.getMetadata().setLabels(Map.of("l1", "v1", "l2", "v2"));
|
|
||||||
indexer.update(labelsIndexName, post2);
|
|
||||||
|
|
||||||
// Verify that the Indexer has the correct indices.
|
|
||||||
JSONAssert.assertEquals("""
|
|
||||||
{
|
|
||||||
"app=myapp": [
|
|
||||||
"post-1"
|
|
||||||
],
|
|
||||||
"env=prod": [
|
|
||||||
"post-1",
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"app=otherapp": [
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"l1=v1": [
|
|
||||||
"post-2"
|
|
||||||
],
|
|
||||||
"l2=v2": [
|
|
||||||
"post-2"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
JsonUtils.objectToJson(indexer.getIndices("labels")),
|
|
||||||
true);
|
|
||||||
|
|
||||||
// Update post1 in the Indexer.
|
|
||||||
post1.getMetadata().setLabels(Map.of("l2", "v2", "l3", "v3"));
|
|
||||||
indexer.update(labelsIndexName, post1);
|
|
||||||
|
|
||||||
// Verify that the Indexer has the correct indices.
|
|
||||||
JSONAssert.assertEquals("""
|
|
||||||
{
|
|
||||||
"env=prod": [
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"app=otherapp": [
|
|
||||||
"post-3"
|
|
||||||
],
|
|
||||||
"l1=v1": [
|
|
||||||
"post-2"
|
|
||||||
],
|
|
||||||
"l2=v2": [
|
|
||||||
"post-1",
|
|
||||||
"post-2"
|
|
||||||
],
|
|
||||||
"l3=v3": [
|
|
||||||
"post-1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
JsonUtils.objectToJson(indexer.getIndices("labels")),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void multiIndexName() {
|
|
||||||
// Create a new Indexer.
|
|
||||||
DefaultIndexer<Post> indexer = new DefaultIndexer<>();
|
|
||||||
|
|
||||||
// Define the IndexFunc for labels.
|
|
||||||
String labelsIndexName = "labels";
|
|
||||||
DefaultIndexer.IndexFunc<Post> labelIndexFunc = labelIndexFunc();
|
|
||||||
indexer.addIndexFunc(labelsIndexName, labelIndexFunc);
|
|
||||||
|
|
||||||
String tagsIndexName = "tags";
|
|
||||||
indexer.addIndexFunc(tagsIndexName, post -> {
|
|
||||||
List<String> tags = post.getSpec().getTags();
|
|
||||||
return tags == null ? Set.of() : Set.copyOf(tags);
|
|
||||||
});
|
|
||||||
|
|
||||||
Post post1 = new Post();
|
|
||||||
post1.setMetadata(new Metadata());
|
|
||||||
post1.getMetadata().setName("post-1");
|
|
||||||
post1.getMetadata().setLabels(Map.of("app", "myapp", "env", "prod"));
|
|
||||||
post1.setSpec(new Post.PostSpec());
|
|
||||||
post1.getSpec().setTags(List.of("t1", "t2"));
|
|
||||||
|
|
||||||
Post post2 = new Post();
|
|
||||||
post2.setMetadata(new Metadata());
|
|
||||||
post2.getMetadata().setName("post-2");
|
|
||||||
post2.getMetadata().setLabels(Map.of("app", "myapp", "env", "test"));
|
|
||||||
post2.setSpec(new Post.PostSpec());
|
|
||||||
post2.getSpec().setTags(List.of("t2", "t3"));
|
|
||||||
|
|
||||||
indexer.add(labelsIndexName, post1);
|
|
||||||
indexer.add(tagsIndexName, post1);
|
|
||||||
|
|
||||||
indexer.add(labelsIndexName, post2);
|
|
||||||
indexer.add(tagsIndexName, post2);
|
|
||||||
|
|
||||||
assertThat(indexer.getByIndex(labelsIndexName, "app=myapp"))
|
|
||||||
.containsExactlyInAnyOrder("post-1", "post-2");
|
|
||||||
assertThat(indexer.getByIndex(tagsIndexName, "t1"))
|
|
||||||
.containsExactlyInAnyOrder("post-1");
|
|
||||||
|
|
||||||
assertThat(indexer.getByIndex(labelsIndexName, "env=test"))
|
|
||||||
.containsExactlyInAnyOrder("post-2");
|
|
||||||
assertThat(indexer.getByIndex(tagsIndexName, "t2"))
|
|
||||||
.containsExactlyInAnyOrder("post-1", "post-2");
|
|
||||||
|
|
||||||
post2.getSpec().setTags(List.of("t1", "t4"));
|
|
||||||
indexer.update(tagsIndexName, post2);
|
|
||||||
|
|
||||||
assertThat(indexer.getByIndex(tagsIndexName, "t1"))
|
|
||||||
.containsExactlyInAnyOrder("post-1", "post-2");
|
|
||||||
assertThat(indexer.getByIndex(tagsIndexName, "t2"))
|
|
||||||
.containsExactlyInAnyOrder("post-1");
|
|
||||||
assertThat(indexer.getByIndex(tagsIndexName, "t4"))
|
|
||||||
.containsExactlyInAnyOrder("post-2");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DefaultIndexer.IndexFunc<Post> labelIndexFunc() {
|
|
||||||
return post -> {
|
|
||||||
Map<String, String> labels = post.getMetadata().getLabels();
|
|
||||||
Set<String> indexKeys = new HashSet<>();
|
|
||||||
if (labels != null) {
|
|
||||||
for (Map.Entry<String, String> entry : labels.entrySet()) {
|
|
||||||
indexKeys.add(entry.getKey() + "=" + entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return indexKeys;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getByIndex() {
|
|
||||||
DefaultIndexer<Post> indexer = new DefaultIndexer<>();
|
|
||||||
String tagsIndexName = "tags";
|
|
||||||
indexer.addIndexFunc(tagsIndexName, post -> {
|
|
||||||
List<String> tags = post.getSpec().getTags();
|
|
||||||
return tags == null ? Set.of() : Set.copyOf(tags);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create some Post objects.
|
|
||||||
Post post1 = new Post();
|
|
||||||
post1.setMetadata(new Metadata());
|
|
||||||
post1.getMetadata().setName("post-1");
|
|
||||||
post1.setSpec(new Post.PostSpec());
|
|
||||||
post1.getSpec().setTags(List.of("t1", "t2"));
|
|
||||||
|
|
||||||
Post post2 = new Post();
|
|
||||||
post2.setMetadata(new Metadata());
|
|
||||||
post2.getMetadata().setName("post-2");
|
|
||||||
post2.setSpec(new Post.PostSpec());
|
|
||||||
post2.getSpec().setTags(List.of("t2", "t3"));
|
|
||||||
|
|
||||||
indexer.add(tagsIndexName, post1);
|
|
||||||
indexer.add(tagsIndexName, post2);
|
|
||||||
|
|
||||||
assertThat(indexer.getByIndex(tagsIndexName, "t1"))
|
|
||||||
.containsExactlyInAnyOrder("post-1");
|
|
||||||
assertThat(indexer.getByIndex(tagsIndexName, "t2"))
|
|
||||||
.containsExactlyInAnyOrder("post-1", "post-2");
|
|
||||||
assertThat(indexer.getByIndex(tagsIndexName, "t3"))
|
|
||||||
.containsExactlyInAnyOrder("post-2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addButNotIndexFunc() {
|
|
||||||
// Create some Post objects.
|
|
||||||
Post post1 = new Post();
|
|
||||||
post1.setMetadata(new Metadata());
|
|
||||||
post1.getMetadata().setName("post-1");
|
|
||||||
post1.setSpec(new Post.PostSpec());
|
|
||||||
post1.getSpec().setTags(List.of("t1", "t2"));
|
|
||||||
|
|
||||||
// Create a new Indexer that indexes Post objects by tags.
|
|
||||||
final DefaultIndexer<Post> indexer = new DefaultIndexer<>();
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> {
|
|
||||||
indexer.add("fake-index-name", post1);
|
|
||||||
}, "Index function not found for index name 'fake-index-name'");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.content;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -12,7 +13,7 @@ import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.extension.index.query.QueryIndexViewImpl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link PostQuery}.
|
* Tests for {@link PostQuery}.
|
||||||
|
@ -32,61 +33,20 @@ class PostQueryTest {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
PostQuery postQuery = new PostQuery(request, "faker");
|
PostQuery postQuery = new PostQuery(request, "faker");
|
||||||
var spec = new Post.PostSpec();
|
|
||||||
var post = new Post();
|
|
||||||
post.setSpec(spec);
|
|
||||||
|
|
||||||
spec.setOwner("another-faker");
|
var listOptions = postQuery.toListOptions();
|
||||||
assertThat(postQuery.toPredicate().test(post)).isFalse();
|
assertThat(listOptions).isNotNull();
|
||||||
|
assertThat(listOptions.getFieldSelector()).isNotNull();
|
||||||
|
var nameEntry =
|
||||||
|
(Collection<Map.Entry<String, String>>) List.of(Map.entry("metadata.name", "faker"));
|
||||||
|
var entry = (Collection<Map.Entry<String, String>>) List.of(Map.entry("faker", "faker"));
|
||||||
|
var indexView =
|
||||||
|
new QueryIndexViewImpl(Map.of("spec.owner", entry, "metadata.name", nameEntry));
|
||||||
|
assertThat(listOptions.getFieldSelector().query().matches(indexView))
|
||||||
|
.containsExactly("faker");
|
||||||
|
|
||||||
spec.setOwner("faker");
|
entry = List.of(Map.entry("another-faker", "user1"));
|
||||||
assertThat(postQuery.toPredicate().test(post)).isTrue();
|
indexView = new QueryIndexViewImpl(Map.of("spec.owner", entry, "metadata.name", nameEntry));
|
||||||
}
|
assertThat(listOptions.getFieldSelector().query().matches(indexView)).isEmpty();
|
||||||
|
|
||||||
@Test
|
|
||||||
void toPredicate() {
|
|
||||||
MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
|
|
||||||
multiValueMap.put("category", List.of("category1", "category2"));
|
|
||||||
MockServerRequest request = MockServerRequest.builder()
|
|
||||||
.queryParams(multiValueMap)
|
|
||||||
.exchange(mock(ServerWebExchange.class))
|
|
||||||
.build();
|
|
||||||
PostQuery postQuery = new PostQuery(request);
|
|
||||||
|
|
||||||
Post post = TestPost.postV1();
|
|
||||||
post.getSpec().setTags(null);
|
|
||||||
post.getStatusOrDefault().setContributors(null);
|
|
||||||
post.getSpec().setCategories(List.of("category1"));
|
|
||||||
boolean test = postQuery.toPredicate().test(post);
|
|
||||||
assertThat(test).isTrue();
|
|
||||||
|
|
||||||
post.getSpec().setTags(List.of("tag1"));
|
|
||||||
test = postQuery.toPredicate().test(post);
|
|
||||||
assertThat(test).isTrue();
|
|
||||||
|
|
||||||
// Do not include tags
|
|
||||||
multiValueMap.put("tag", List.of("tag2"));
|
|
||||||
post.getSpec().setTags(List.of("tag1"));
|
|
||||||
post.getSpec().setCategories(null);
|
|
||||||
test = postQuery.toPredicate().test(post);
|
|
||||||
assertThat(test).isFalse();
|
|
||||||
|
|
||||||
multiValueMap.put("tag", List.of());
|
|
||||||
multiValueMap.remove("category");
|
|
||||||
request = MockServerRequest.builder()
|
|
||||||
.exchange(mock(ServerWebExchange.class))
|
|
||||||
.queryParams(multiValueMap).build();
|
|
||||||
postQuery = new PostQuery(request);
|
|
||||||
post.getSpec().setTags(List.of());
|
|
||||||
test = postQuery.toPredicate().test(post);
|
|
||||||
assertThat(test).isTrue();
|
|
||||||
|
|
||||||
multiValueMap.put("labelSelector", List.of("hello"));
|
|
||||||
test = postQuery.toPredicate().test(post);
|
|
||||||
assertThat(test).isFalse();
|
|
||||||
|
|
||||||
post.getMetadata().setLabels(Map.of("hello", "world"));
|
|
||||||
test = postQuery.toPredicate().test(post);
|
|
||||||
assertThat(test).isTrue();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,18 +10,19 @@ import static org.mockito.Mockito.verify;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.json.JSONException;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import run.halo.app.content.TestPost;
|
import run.halo.app.content.TestPost;
|
||||||
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
|
import run.halo.app.content.permalinks.CategoryPermalinkPolicy;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ class CategoryReconcilerTest {
|
||||||
private CategoryReconciler categoryReconciler;
|
private CategoryReconciler categoryReconciler;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reconcileStatusPostForCategoryA() throws JSONException {
|
void reconcileStatusPostForCategoryA() {
|
||||||
reconcileStatusPostPilling("category-A");
|
reconcileStatusPostPilling("category-A");
|
||||||
|
|
||||||
ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
|
ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
|
||||||
|
@ -54,7 +55,7 @@ class CategoryReconcilerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reconcileStatusPostForCategoryB() throws JSONException {
|
void reconcileStatusPostForCategoryB() {
|
||||||
reconcileStatusPostPilling("category-B");
|
reconcileStatusPostPilling("category-B");
|
||||||
ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
|
ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
|
||||||
verify(client, times(3)).update(captor.capture());
|
verify(client, times(3)).update(captor.capture());
|
||||||
|
@ -64,7 +65,7 @@ class CategoryReconcilerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reconcileStatusPostForCategoryC() throws JSONException {
|
void reconcileStatusPostForCategoryC() {
|
||||||
reconcileStatusPostPilling("category-C");
|
reconcileStatusPostPilling("category-C");
|
||||||
ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
|
ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
|
||||||
verify(client, times(3)).update(captor.capture());
|
verify(client, times(3)).update(captor.capture());
|
||||||
|
@ -74,7 +75,7 @@ class CategoryReconcilerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reconcileStatusPostForCategoryD() throws JSONException {
|
void reconcileStatusPostForCategoryD() {
|
||||||
reconcileStatusPostPilling("category-D");
|
reconcileStatusPostPilling("category-D");
|
||||||
ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
|
ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
|
||||||
verify(client, times(3)).update(captor.capture());
|
verify(client, times(3)).update(captor.capture());
|
||||||
|
@ -89,9 +90,9 @@ class CategoryReconcilerTest {
|
||||||
.thenReturn(Optional.of(category));
|
.thenReturn(Optional.of(category));
|
||||||
});
|
});
|
||||||
|
|
||||||
lenient().when(client.list(eq(Post.class), any(), any()))
|
lenient().when(client.listAll(eq(Post.class), any(ListOptions.class), any(Sort.class)))
|
||||||
.thenReturn(posts());
|
.thenReturn(posts());
|
||||||
lenient().when(client.list(eq(Category.class), any(), any()))
|
lenient().when(client.listAll(eq(Category.class), any(), any()))
|
||||||
.thenReturn(categories());
|
.thenReturn(categories());
|
||||||
|
|
||||||
Reconciler.Result result =
|
Reconciler.Result result =
|
||||||
|
|
|
@ -85,7 +85,7 @@ class PostReconcilerTest {
|
||||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||||
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
||||||
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
|
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
|
||||||
when(client.list(eq(Snapshot.class), any(), any()))
|
when(client.listAll(eq(Snapshot.class), any(), any()))
|
||||||
.thenReturn(List.of(snapshotV1, snapshotV2));
|
.thenReturn(List.of(snapshotV1, snapshotV2));
|
||||||
|
|
||||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||||
|
@ -126,7 +126,7 @@ class PostReconcilerTest {
|
||||||
Snapshot snapshotV1 = TestPost.snapshotV1();
|
Snapshot snapshotV1 = TestPost.snapshotV1();
|
||||||
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
||||||
|
|
||||||
when(client.list(eq(Snapshot.class), any(), any()))
|
when(client.listAll(eq(Snapshot.class), any(), any()))
|
||||||
.thenReturn(List.of(snapshotV1, snapshotV2));
|
.thenReturn(List.of(snapshotV1, snapshotV2));
|
||||||
|
|
||||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||||
|
@ -162,7 +162,7 @@ class PostReconcilerTest {
|
||||||
when(client.fetch(eq(Snapshot.class), eq(post.getSpec().getReleaseSnapshot())))
|
when(client.fetch(eq(Snapshot.class), eq(post.getSpec().getReleaseSnapshot())))
|
||||||
.thenReturn(Optional.of(snapshotV2));
|
.thenReturn(Optional.of(snapshotV2));
|
||||||
|
|
||||||
when(client.list(eq(Snapshot.class), any(), any()))
|
when(client.listAll(eq(Snapshot.class), any(), any()))
|
||||||
.thenReturn(List.of());
|
.thenReturn(List.of());
|
||||||
|
|
||||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||||
|
@ -191,7 +191,7 @@ class PostReconcilerTest {
|
||||||
.rawType("markdown")
|
.rawType("markdown")
|
||||||
.build()));
|
.build()));
|
||||||
|
|
||||||
when(client.list(eq(Snapshot.class), any(), any()))
|
when(client.listAll(eq(Snapshot.class), any(), any()))
|
||||||
.thenReturn(List.of());
|
.thenReturn(List.of());
|
||||||
|
|
||||||
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
ArgumentCaptor<Post> captor = ArgumentCaptor.forClass(Post.class);
|
||||||
|
|
|
@ -95,7 +95,7 @@ class SinglePageReconcilerTest {
|
||||||
Snapshot snapshotV2 = TestPost.snapshotV2();
|
Snapshot snapshotV2 = TestPost.snapshotV2();
|
||||||
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
snapshotV1.getSpec().setContributors(Set.of("guqing"));
|
||||||
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
|
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
|
||||||
when(client.list(eq(Snapshot.class), any(), any()))
|
when(client.listAll(eq(Snapshot.class), any(), any()))
|
||||||
.thenReturn(List.of(snapshotV1, snapshotV2));
|
.thenReturn(List.of(snapshotV1, snapshotV2));
|
||||||
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
|
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ class SinglePageReconcilerTest {
|
||||||
when(client.fetch(eq(Snapshot.class), eq(page.getSpec().getReleaseSnapshot())))
|
when(client.fetch(eq(Snapshot.class), eq(page.getSpec().getReleaseSnapshot())))
|
||||||
.thenReturn(Optional.of(snapshotV2));
|
.thenReturn(Optional.of(snapshotV2));
|
||||||
|
|
||||||
when(client.list(eq(Snapshot.class), any(), any()))
|
when(client.listAll(eq(Snapshot.class), any(), any()))
|
||||||
.thenReturn(List.of());
|
.thenReturn(List.of());
|
||||||
|
|
||||||
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
||||||
|
@ -186,7 +186,7 @@ class SinglePageReconcilerTest {
|
||||||
.build())
|
.build())
|
||||||
);
|
);
|
||||||
|
|
||||||
when(client.list(eq(Snapshot.class), any(), any()))
|
when(client.listAll(eq(Snapshot.class), any(), any()))
|
||||||
.thenReturn(List.of());
|
.thenReturn(List.of());
|
||||||
|
|
||||||
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
|
||||||
|
|
|
@ -10,15 +10,14 @@ import static org.mockito.Mockito.when;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import run.halo.app.content.PostIndexInformer;
|
|
||||||
import run.halo.app.content.permalinks.TagPermalinkPolicy;
|
import run.halo.app.content.permalinks.TagPermalinkPolicy;
|
||||||
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
@ -37,9 +36,6 @@ class TagReconcilerTest {
|
||||||
@Mock
|
@Mock
|
||||||
private TagPermalinkPolicy tagPermalinkPolicy;
|
private TagPermalinkPolicy tagPermalinkPolicy;
|
||||||
|
|
||||||
@Mock
|
|
||||||
private PostIndexInformer postIndexInformer;
|
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private TagReconciler tagReconciler;
|
private TagReconciler tagReconciler;
|
||||||
|
|
||||||
|
@ -48,8 +44,7 @@ class TagReconcilerTest {
|
||||||
Tag tag = tag();
|
Tag tag = tag();
|
||||||
when(client.fetch(eq(Tag.class), eq("fake-tag")))
|
when(client.fetch(eq(Tag.class), eq("fake-tag")))
|
||||||
.thenReturn(Optional.of(tag));
|
.thenReturn(Optional.of(tag));
|
||||||
when(postIndexInformer.getByTagName(eq("fake-tag")))
|
when(client.listAll(eq(Post.class), any(), any())).thenReturn(List.of());
|
||||||
.thenReturn(Set.of());
|
|
||||||
when(tagPermalinkPolicy.permalink(any()))
|
when(tagPermalinkPolicy.permalink(any()))
|
||||||
.thenAnswer(arg -> "/tags/" + tag.getSpec().getSlug());
|
.thenAnswer(arg -> "/tags/" + tag.getSpec().getSlug());
|
||||||
ArgumentCaptor<Tag> captor = ArgumentCaptor.forClass(Tag.class);
|
ArgumentCaptor<Tag> captor = ArgumentCaptor.forClass(Tag.class);
|
||||||
|
@ -85,8 +80,8 @@ class TagReconcilerTest {
|
||||||
Tag tag = tag();
|
Tag tag = tag();
|
||||||
when(client.fetch(eq(Tag.class), eq("fake-tag")))
|
when(client.fetch(eq(Tag.class), eq("fake-tag")))
|
||||||
.thenReturn(Optional.of(tag));
|
.thenReturn(Optional.of(tag));
|
||||||
when(postIndexInformer.getByTagName(eq("fake-tag")))
|
when(client.listAll(eq(Post.class), any(), any()))
|
||||||
.thenReturn(Set.of("fake-post-1", "fake-post-3"));
|
.thenReturn(List.of(createPost("fake-post-1"), createPost("fake-post-2")));
|
||||||
|
|
||||||
ArgumentCaptor<Tag> captor = ArgumentCaptor.forClass(Tag.class);
|
ArgumentCaptor<Tag> captor = ArgumentCaptor.forClass(Tag.class);
|
||||||
tagReconciler.reconcile(new TagReconciler.Request("fake-tag"));
|
tagReconciler.reconcile(new TagReconciler.Request("fake-tag"));
|
||||||
|
@ -96,6 +91,14 @@ class TagReconcilerTest {
|
||||||
assertThat(allValues.get(1).getStatusOrDefault().getVisiblePostCount()).isEqualTo(0);
|
assertThat(allValues.get(1).getStatusOrDefault().getVisiblePostCount()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Post createPost(String name) {
|
||||||
|
var post = new Post();
|
||||||
|
post.setMetadata(new Metadata());
|
||||||
|
post.getMetadata().setName(name);
|
||||||
|
post.setSpec(new Post.PostSpec());
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
Tag tag() {
|
Tag tag() {
|
||||||
Tag tag = new Tag();
|
Tag tag = new Tag();
|
||||||
tag.setMetadata(new Metadata());
|
tag.setMetadata(new Metadata());
|
||||||
|
|
|
@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class ListResultTest {
|
class ListResultTest {
|
||||||
|
@ -53,6 +54,15 @@ class ListResultTest {
|
||||||
assertSubList(list);
|
assertSubList(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void firstTest() {
|
||||||
|
var listResult = new ListResult<>(List.of());
|
||||||
|
assertEquals(Optional.empty(), ListResult.first(listResult));
|
||||||
|
|
||||||
|
listResult = new ListResult<>(1, 10, 1, List.of("A"));
|
||||||
|
assertEquals(Optional.of("A"), ListResult.first(listResult));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSubList(List<Integer> list) {
|
private void assertSubList(List<Integer> list) {
|
||||||
var result = ListResult.subList(list, 0, 0);
|
var result = ListResult.subList(list, 0, 0);
|
||||||
assertEquals(list, result);
|
assertEquals(list, result);
|
||||||
|
|
|
@ -2,7 +2,6 @@ package run.halo.app.theme.endpoint;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -19,8 +18,10 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
import run.halo.app.extension.GroupVersion;
|
import run.halo.app.extension.GroupVersion;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
|
@ -52,7 +53,7 @@ class CategoryQueryEndpointTest {
|
||||||
@Test
|
@Test
|
||||||
void listCategories() {
|
void listCategories() {
|
||||||
ListResult<Category> listResult = new ListResult<>(List.of());
|
ListResult<Category> listResult = new ListResult<>(List.of());
|
||||||
when(client.list(eq(Category.class), any(), any(), anyInt(), anyInt()))
|
when(client.listBy(eq(Category.class), any(ListOptions.class), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(listResult));
|
.thenReturn(Mono.just(listResult));
|
||||||
|
|
||||||
webTestClient.get()
|
webTestClient.get()
|
||||||
|
@ -84,7 +85,7 @@ class CategoryQueryEndpointTest {
|
||||||
@Test
|
@Test
|
||||||
void listPostsByCategoryName() {
|
void listPostsByCategoryName() {
|
||||||
ListResult<ListedPostVo> listResult = new ListResult<>(List.of());
|
ListResult<ListedPostVo> listResult = new ListResult<>(List.of());
|
||||||
when(postPublicQueryService.list(anyInt(), anyInt(), any(), any()))
|
when(postPublicQueryService.list(any(), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(listResult));
|
.thenReturn(Mono.just(listResult));
|
||||||
|
|
||||||
webTestClient.get()
|
webTestClient.get()
|
||||||
|
|
|
@ -2,7 +2,6 @@ package run.halo.app.theme.endpoint;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
@ -20,6 +19,7 @@ import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.extension.GroupVersion;
|
import run.halo.app.extension.GroupVersion;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.theme.finders.PostFinder;
|
import run.halo.app.theme.finders.PostFinder;
|
||||||
import run.halo.app.theme.finders.PostPublicQueryService;
|
import run.halo.app.theme.finders.PostPublicQueryService;
|
||||||
import run.halo.app.theme.finders.vo.ListedPostVo;
|
import run.halo.app.theme.finders.vo.ListedPostVo;
|
||||||
|
@ -55,7 +55,7 @@ class PostQueryEndpointTest {
|
||||||
@Test
|
@Test
|
||||||
public void listPosts() {
|
public void listPosts() {
|
||||||
ListResult<ListedPostVo> result = new ListResult<>(List.of());
|
ListResult<ListedPostVo> result = new ListResult<>(List.of());
|
||||||
when(postPublicQueryService.list(anyInt(), anyInt(), any(), any()))
|
when(postPublicQueryService.list(any(), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(result));
|
.thenReturn(Mono.just(result));
|
||||||
|
|
||||||
webClient.get().uri("/posts")
|
webClient.get().uri("/posts")
|
||||||
|
@ -65,7 +65,7 @@ class PostQueryEndpointTest {
|
||||||
.expectBody()
|
.expectBody()
|
||||||
.jsonPath("$.items").isArray();
|
.jsonPath("$.items").isArray();
|
||||||
|
|
||||||
verify(postPublicQueryService).list(anyInt(), anyInt(), any(), any());
|
verify(postPublicQueryService).list(any(), any(PageRequest.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -2,7 +2,6 @@ package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -18,11 +17,14 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.skyscreamer.jsonassert.JSONAssert;
|
import org.skyscreamer.jsonassert.JSONAssert;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Category;
|
import run.halo.app.core.extension.content.Category;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
import run.halo.app.theme.finders.vo.CategoryTreeVo;
|
import run.halo.app.theme.finders.vo.CategoryTreeVo;
|
||||||
|
@ -85,7 +87,7 @@ class CategoryFinderImplTest {
|
||||||
categories().stream()
|
categories().stream()
|
||||||
.sorted(CategoryFinderImpl.defaultComparator())
|
.sorted(CategoryFinderImpl.defaultComparator())
|
||||||
.toList());
|
.toList());
|
||||||
when(client.list(eq(Category.class), eq(null), any(), anyInt(), anyInt()))
|
when(client.listBy(eq(Category.class), any(ListOptions.class), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(categories));
|
.thenReturn(Mono.just(categories));
|
||||||
ListResult<CategoryVo> list = categoryFinder.list(1, 10).block();
|
ListResult<CategoryVo> list = categoryFinder.list(1, 10).block();
|
||||||
assertThat(list.getItems()).hasSize(3);
|
assertThat(list.getItems()).hasSize(3);
|
||||||
|
@ -95,7 +97,7 @@ class CategoryFinderImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void listAsTree() {
|
void listAsTree() {
|
||||||
when(client.list(eq(Category.class), eq(null), any()))
|
when(client.listAll(eq(Category.class), any(ListOptions.class), any(Sort.class)))
|
||||||
.thenReturn(Flux.fromIterable(categoriesForTree()));
|
.thenReturn(Flux.fromIterable(categoriesForTree()));
|
||||||
List<CategoryTreeVo> treeVos = categoryFinder.listAsTree().collectList().block();
|
List<CategoryTreeVo> treeVos = categoryFinder.listAsTree().collectList().block();
|
||||||
assertThat(treeVos).hasSize(1);
|
assertThat(treeVos).hasSize(1);
|
||||||
|
@ -103,7 +105,7 @@ class CategoryFinderImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void listSubTreeByName() {
|
void listSubTreeByName() {
|
||||||
when(client.list(eq(Category.class), eq(null), any()))
|
when(client.listAll(eq(Category.class), any(ListOptions.class), any(Sort.class)))
|
||||||
.thenReturn(Flux.fromIterable(categoriesForTree()));
|
.thenReturn(Flux.fromIterable(categoriesForTree()));
|
||||||
List<CategoryTreeVo> treeVos = categoryFinder.listAsTree("E").collectList().block();
|
List<CategoryTreeVo> treeVos = categoryFinder.listAsTree("E").collectList().block();
|
||||||
assertThat(treeVos.get(0).getMetadata().getName()).isEqualTo("E");
|
assertThat(treeVos.get(0).getMetadata().getName()).isEqualTo("E");
|
||||||
|
@ -119,7 +121,7 @@ class CategoryFinderImplTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void listAsTreeMore() {
|
void listAsTreeMore() {
|
||||||
when(client.list(eq(Category.class), eq(null), any()))
|
when(client.listAll(eq(Category.class), any(ListOptions.class), any(Sort.class)))
|
||||||
.thenReturn(Flux.fromIterable(moreCategories()));
|
.thenReturn(Flux.fromIterable(moreCategories()));
|
||||||
List<CategoryTreeVo> treeVos = categoryFinder.listAsTree().collectList().block();
|
List<CategoryTreeVo> treeVos = categoryFinder.listAsTree().collectList().block();
|
||||||
String s = visualizeTree(treeVos);
|
String s = visualizeTree(treeVos);
|
||||||
|
|
|
@ -2,7 +2,6 @@ package run.halo.app.theme.finders.impl;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
@ -11,8 +10,6 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
import org.apache.logging.log4j.util.Strings;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
|
@ -23,6 +20,7 @@ import run.halo.app.content.PostService;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.extension.ListResult;
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.metrics.CounterService;
|
import run.halo.app.metrics.CounterService;
|
||||||
import run.halo.app.theme.finders.CategoryFinder;
|
import run.halo.app.theme.finders.CategoryFinder;
|
||||||
|
@ -67,15 +65,6 @@ class PostFinderImplTest {
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private PostFinderImpl postFinder;
|
private PostFinderImpl postFinder;
|
||||||
|
|
||||||
@Test
|
|
||||||
void compare() {
|
|
||||||
List<String> strings = posts().stream().sorted(PostFinderImpl.defaultComparator())
|
|
||||||
.map(post -> post.getMetadata().getName())
|
|
||||||
.toList();
|
|
||||||
assertThat(strings).isEqualTo(
|
|
||||||
List.of("post-6", "post-2", "post-1", "post-5", "post-4", "post-3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void predicate() {
|
void predicate() {
|
||||||
Predicate<Post> predicate = new DefaultQueryPostPredicateResolver().getPredicate().block();
|
Predicate<Post> predicate = new DefaultQueryPostPredicateResolver().getPredicate().block();
|
||||||
|
@ -93,7 +82,7 @@ class PostFinderImplTest {
|
||||||
.map(ListedPostVo::from)
|
.map(ListedPostVo::from)
|
||||||
.toList();
|
.toList();
|
||||||
ListResult<ListedPostVo> listResult = new ListResult<>(1, 10, 3, listedPostVos);
|
ListResult<ListedPostVo> listResult = new ListResult<>(1, 10, 3, listedPostVos);
|
||||||
when(publicQueryService.list(anyInt(), anyInt(), any(), any()))
|
when(publicQueryService.list(any(), any(PageRequest.class)))
|
||||||
.thenReturn(Mono.just(listResult));
|
.thenReturn(Mono.just(listResult));
|
||||||
|
|
||||||
ListResult<PostArchiveVo> archives = postFinder.archives(1, 10).block();
|
ListResult<PostArchiveVo> archives = postFinder.archives(1, 10).block();
|
||||||
|
@ -112,22 +101,6 @@ class PostFinderImplTest {
|
||||||
assertThat(items.get(1).getMonths().get(0).getMonth()).isEqualTo("01");
|
assertThat(items.get(1).getMonths().get(0).getMonth()).isEqualTo("01");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void fixedSizeSlidingWindow() {
|
|
||||||
PostFinderImpl.FixedSizeSlidingWindow<Integer>
|
|
||||||
window = new PostFinderImpl.FixedSizeSlidingWindow<>(3);
|
|
||||||
|
|
||||||
List<String> list = new ArrayList<>();
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
window.add(i);
|
|
||||||
list.add(Strings.join(window.elements(), ','));
|
|
||||||
}
|
|
||||||
assertThat(list).isEqualTo(
|
|
||||||
List.of("0", "0,1", "0,1,2", "1,2,3", "2,3,4", "3,4,5", "4,5,6", "5,6,7", "6,7,8",
|
|
||||||
"7,8,9")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void postPreviousNextPair() {
|
void postPreviousNextPair() {
|
||||||
List<String> postNames = new ArrayList<>();
|
List<String> postNames = new ArrayList<>();
|
||||||
|
@ -136,28 +109,27 @@ class PostFinderImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// post-0, post-1, post-2
|
// post-0, post-1, post-2
|
||||||
Pair<String, String> previousNextPair =
|
var previousNextPair = PostFinderImpl.findPostNavigation(postNames, "post-0");
|
||||||
PostFinderImpl.postPreviousNextPair(postNames, "post-0");
|
assertThat(previousNextPair.prev()).isNull();
|
||||||
assertThat(previousNextPair.getLeft()).isNull();
|
assertThat(previousNextPair.next()).isEqualTo("post-1");
|
||||||
assertThat(previousNextPair.getRight()).isEqualTo("post-1");
|
|
||||||
|
|
||||||
previousNextPair = PostFinderImpl.postPreviousNextPair(postNames, "post-1");
|
previousNextPair = PostFinderImpl.findPostNavigation(postNames, "post-1");
|
||||||
assertThat(previousNextPair.getLeft()).isEqualTo("post-0");
|
assertThat(previousNextPair.prev()).isEqualTo("post-0");
|
||||||
assertThat(previousNextPair.getRight()).isEqualTo("post-2");
|
assertThat(previousNextPair.next()).isEqualTo("post-2");
|
||||||
|
|
||||||
// post-1, post-2, post-3
|
// post-1, post-2, post-3
|
||||||
previousNextPair = PostFinderImpl.postPreviousNextPair(postNames, "post-2");
|
previousNextPair = PostFinderImpl.findPostNavigation(postNames, "post-2");
|
||||||
assertThat(previousNextPair.getLeft()).isEqualTo("post-1");
|
assertThat(previousNextPair.prev()).isEqualTo("post-1");
|
||||||
assertThat(previousNextPair.getRight()).isEqualTo("post-3");
|
assertThat(previousNextPair.next()).isEqualTo("post-3");
|
||||||
|
|
||||||
// post-7, post-8, post-9
|
// post-7, post-8, post-9
|
||||||
previousNextPair = PostFinderImpl.postPreviousNextPair(postNames, "post-8");
|
previousNextPair = PostFinderImpl.findPostNavigation(postNames, "post-8");
|
||||||
assertThat(previousNextPair.getLeft()).isEqualTo("post-7");
|
assertThat(previousNextPair.prev()).isEqualTo("post-7");
|
||||||
assertThat(previousNextPair.getRight()).isEqualTo("post-9");
|
assertThat(previousNextPair.next()).isEqualTo("post-9");
|
||||||
|
|
||||||
previousNextPair = PostFinderImpl.postPreviousNextPair(postNames, "post-9");
|
previousNextPair = PostFinderImpl.findPostNavigation(postNames, "post-9");
|
||||||
assertThat(previousNextPair.getLeft()).isEqualTo("post-8");
|
assertThat(previousNextPair.prev()).isEqualTo("post-8");
|
||||||
assertThat(previousNextPair.getRight()).isNull();
|
assertThat(previousNextPair.next()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Post> postsForArchives() {
|
List<Post> postsForArchives() {
|
||||||
|
|
|
@ -16,9 +16,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.skyscreamer.jsonassert.JSONAssert;
|
import org.skyscreamer.jsonassert.JSONAssert;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
@ -77,7 +79,7 @@ class TagFinderImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void listAll() {
|
void listAll() {
|
||||||
when(client.list(eq(Tag.class), eq(null), any()))
|
when(client.listAll(eq(Tag.class), any(ListOptions.class), any(Sort.class)))
|
||||||
.thenReturn(Flux.fromIterable(
|
.thenReturn(Flux.fromIterable(
|
||||||
tags().stream().sorted(TagFinderImpl.DEFAULT_COMPARATOR.reversed()).toList()
|
tags().stream().sorted(TagFinderImpl.DEFAULT_COMPARATOR.reversed()).toList()
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,16 +4,18 @@ import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.content.Tag;
|
import run.halo.app.core.extension.content.Tag;
|
||||||
|
import run.halo.app.extension.ListResult;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.theme.finders.PostFinder;
|
import run.halo.app.theme.finders.PostFinder;
|
||||||
import run.halo.app.theme.finders.TagFinder;
|
import run.halo.app.theme.finders.TagFinder;
|
||||||
|
@ -39,7 +41,8 @@ class TagPostRouteFactoryTest extends RouteFactoryTestSuite {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void create() {
|
void create() {
|
||||||
when(client.list(eq(Tag.class), any(), eq(null))).thenReturn(Flux.empty());
|
when(client.listBy(eq(Tag.class), any(), any(PageRequest.class)))
|
||||||
|
.thenReturn(Mono.just(ListResult.emptyResult()));
|
||||||
WebTestClient webTestClient = getWebTestClient(tagPostRouteFactory.create("/new-tags"));
|
WebTestClient webTestClient = getWebTestClient(tagPostRouteFactory.create("/new-tags"));
|
||||||
|
|
||||||
webTestClient.get()
|
webTestClient.get()
|
||||||
|
@ -52,7 +55,8 @@ class TagPostRouteFactoryTest extends RouteFactoryTestSuite {
|
||||||
tag.getMetadata().setName("fake-tag-name");
|
tag.getMetadata().setName("fake-tag-name");
|
||||||
tag.setSpec(new Tag.TagSpec());
|
tag.setSpec(new Tag.TagSpec());
|
||||||
tag.getSpec().setSlug("tag-slug-2");
|
tag.getSpec().setSlug("tag-slug-2");
|
||||||
when(client.list(eq(Tag.class), any(), eq(null))).thenReturn(Flux.just(tag));
|
when(client.listBy(eq(Tag.class), any(), any(PageRequest.class)))
|
||||||
|
.thenReturn(Mono.just(new ListResult<>(List.of(tag))));
|
||||||
when(tagFinder.getByName(eq(tag.getMetadata().getName())))
|
when(tagFinder.getByName(eq(tag.getMetadata().getName())))
|
||||||
.thenReturn(Mono.just(TagVo.from(tag)));
|
.thenReturn(Mono.just(TagVo.from(tag)));
|
||||||
webTestClient.get()
|
webTestClient.get()
|
||||||
|
|
|
@ -59,11 +59,11 @@ const {
|
||||||
return data.items;
|
return data.items;
|
||||||
},
|
},
|
||||||
refetchInterval: (data) => {
|
refetchInterval: (data) => {
|
||||||
const deletingPosts = data?.filter(
|
const deletingPosts = data?.some(
|
||||||
(post) =>
|
(post) =>
|
||||||
!!post.post.metadata.deletionTimestamp || !post.post.spec.deleted
|
!!post.post.metadata.deletionTimestamp || !post.post.spec.deleted
|
||||||
);
|
);
|
||||||
return deletingPosts?.length ? 1000 : false;
|
return deletingPosts ? 1000 : false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -113,21 +113,23 @@ const {
|
||||||
keyword,
|
keyword,
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
let categories: string[] | undefined;
|
|
||||||
let tags: string[] | undefined;
|
|
||||||
let contributors: string[] | undefined;
|
|
||||||
const labelSelector: string[] = ["content.halo.run/deleted=false"];
|
const labelSelector: string[] = ["content.halo.run/deleted=false"];
|
||||||
|
const fieldSelector: string[] = [];
|
||||||
|
|
||||||
if (selectedCategory.value) {
|
if (selectedCategory.value) {
|
||||||
categories = [selectedCategory.value];
|
fieldSelector.push(`spec.categories=${selectedCategory.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedTag.value) {
|
if (selectedTag.value) {
|
||||||
tags = [selectedTag.value];
|
fieldSelector.push(`spec.tags=${selectedTag.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedContributor.value) {
|
if (selectedContributor.value) {
|
||||||
contributors = [selectedContributor.value];
|
fieldSelector.push(`status.contributors=${selectedContributor.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedVisible.value) {
|
||||||
|
fieldSelector.push(`spec.visible=${selectedVisible.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedPublishStatus.value !== undefined) {
|
if (selectedPublishStatus.value !== undefined) {
|
||||||
|
@ -138,14 +140,11 @@ const {
|
||||||
|
|
||||||
const { data } = await apiClient.post.listPosts({
|
const { data } = await apiClient.post.listPosts({
|
||||||
labelSelector,
|
labelSelector,
|
||||||
|
fieldSelector,
|
||||||
page: page.value,
|
page: page.value,
|
||||||
size: size.value,
|
size: size.value,
|
||||||
visible: selectedVisible.value,
|
|
||||||
sort: [selectedSort.value].filter(Boolean) as string[],
|
sort: [selectedSort.value].filter(Boolean) as string[],
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
category: categories,
|
|
||||||
tag: tags,
|
|
||||||
contributor: contributors,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
total.value = data.total;
|
total.value = data.total;
|
||||||
|
@ -155,7 +154,7 @@ const {
|
||||||
return data.items;
|
return data.items;
|
||||||
},
|
},
|
||||||
refetchInterval: (data) => {
|
refetchInterval: (data) => {
|
||||||
const abnormalPosts = data?.filter((post) => {
|
const abnormalPosts = data?.some((post) => {
|
||||||
const { spec, metadata, status } = post.post;
|
const { spec, metadata, status } = post.post;
|
||||||
return (
|
return (
|
||||||
spec.deleted ||
|
spec.deleted ||
|
||||||
|
@ -164,7 +163,7 @@ const {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return abnormalPosts?.length ? 1000 : false;
|
return abnormalPosts ? 1000 : false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -407,19 +406,19 @@ watch(selectedPostNames, (newValue) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('core.post.filters.sort.items.publish_time_desc'),
|
label: t('core.post.filters.sort.items.publish_time_desc'),
|
||||||
value: 'publishTime,desc',
|
value: 'spec.publishTime,desc',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('core.post.filters.sort.items.publish_time_asc'),
|
label: t('core.post.filters.sort.items.publish_time_asc'),
|
||||||
value: 'publishTime,asc',
|
value: 'spec.publishTime,asc',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('core.post.filters.sort.items.create_time_desc'),
|
label: t('core.post.filters.sort.items.create_time_desc'),
|
||||||
value: 'creationTimestamp,desc',
|
value: 'metadata.creationTimestamp,desc',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('core.post.filters.sort.items.create_time_asc'),
|
label: t('core.post.filters.sort.items.create_time_asc'),
|
||||||
value: 'creationTimestamp,asc',
|
value: 'metadata.creationTimestamp,asc',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -32,6 +32,7 @@ export function usePostCategory(): usePostCategoryReturn {
|
||||||
await apiClient.extension.category.listcontentHaloRunV1alpha1Category({
|
await apiClient.extension.category.listcontentHaloRunV1alpha1Category({
|
||||||
page: 0,
|
page: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
|
sort: ["metadata.creationTimestamp,desc"],
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.items;
|
return data.items;
|
||||||
|
|
|
@ -26,6 +26,7 @@ export function usePostTag(): usePostTagReturn {
|
||||||
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({
|
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({
|
||||||
page: 0,
|
page: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
|
sort: ["metadata.creationTimestamp,desc"],
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.items;
|
return data.items;
|
||||||
|
|
|
@ -21,7 +21,7 @@ const { data } = useQuery<ListedPost[]>({
|
||||||
`${postLabels.DELETED}=false`,
|
`${postLabels.DELETED}=false`,
|
||||||
`${postLabels.PUBLISHED}=true`,
|
`${postLabels.PUBLISHED}=true`,
|
||||||
],
|
],
|
||||||
sort: ["publishTime,desc"],
|
sort: ["spec.publishTime,desc"],
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
});
|
});
|
||||||
|
|
|
@ -222,8 +222,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* List posts.
|
* List posts.
|
||||||
* @param {Array<string>} [category]
|
|
||||||
* @param {Array<string>} [contributor]
|
|
||||||
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
||||||
* @param {string} [keyword] Posts filtered by keyword.
|
* @param {string} [keyword] Posts filtered by keyword.
|
||||||
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
||||||
|
@ -231,14 +229,10 @@ export const ApiConsoleHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
* @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase]
|
* @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase]
|
||||||
* @param {number} [size] Size of one page. Zero indicates no limit.
|
* @param {number} [size] Size of one page. Zero indicates no limit.
|
||||||
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
|
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
|
||||||
* @param {Array<string>} [tag]
|
|
||||||
* @param {'PUBLIC' | 'INTERNAL' | 'PRIVATE'} [visible]
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
listPosts: async (
|
listPosts: async (
|
||||||
category?: Array<string>,
|
|
||||||
contributor?: Array<string>,
|
|
||||||
fieldSelector?: Array<string>,
|
fieldSelector?: Array<string>,
|
||||||
keyword?: string,
|
keyword?: string,
|
||||||
labelSelector?: Array<string>,
|
labelSelector?: Array<string>,
|
||||||
|
@ -246,8 +240,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED",
|
publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED",
|
||||||
size?: number,
|
size?: number,
|
||||||
sort?: Array<string>,
|
sort?: Array<string>,
|
||||||
tag?: Array<string>,
|
|
||||||
visible?: "PUBLIC" | "INTERNAL" | "PRIVATE",
|
|
||||||
options: AxiosRequestConfig = {}
|
options: AxiosRequestConfig = {}
|
||||||
): Promise<RequestArgs> => {
|
): Promise<RequestArgs> => {
|
||||||
const localVarPath = `/apis/api.console.halo.run/v1alpha1/posts`;
|
const localVarPath = `/apis/api.console.halo.run/v1alpha1/posts`;
|
||||||
|
@ -274,14 +266,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
// http bearer authentication required
|
// http bearer authentication required
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration);
|
await setBearerAuthToObject(localVarHeaderParameter, configuration);
|
||||||
|
|
||||||
if (category) {
|
|
||||||
localVarQueryParameter["category"] = Array.from(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contributor) {
|
|
||||||
localVarQueryParameter["contributor"] = Array.from(contributor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldSelector) {
|
if (fieldSelector) {
|
||||||
localVarQueryParameter["fieldSelector"] = fieldSelector;
|
localVarQueryParameter["fieldSelector"] = fieldSelector;
|
||||||
}
|
}
|
||||||
|
@ -310,14 +294,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
localVarQueryParameter["sort"] = Array.from(sort);
|
localVarQueryParameter["sort"] = Array.from(sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag) {
|
|
||||||
localVarQueryParameter["tag"] = Array.from(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visible !== undefined) {
|
|
||||||
localVarQueryParameter["visible"] = visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions =
|
let headersFromBaseOptions =
|
||||||
baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
@ -710,8 +686,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiFp = function (
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* List posts.
|
* List posts.
|
||||||
* @param {Array<string>} [category]
|
|
||||||
* @param {Array<string>} [contributor]
|
|
||||||
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
||||||
* @param {string} [keyword] Posts filtered by keyword.
|
* @param {string} [keyword] Posts filtered by keyword.
|
||||||
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
||||||
|
@ -719,14 +693,10 @@ export const ApiConsoleHaloRunV1alpha1PostApiFp = function (
|
||||||
* @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase]
|
* @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase]
|
||||||
* @param {number} [size] Size of one page. Zero indicates no limit.
|
* @param {number} [size] Size of one page. Zero indicates no limit.
|
||||||
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
|
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
|
||||||
* @param {Array<string>} [tag]
|
|
||||||
* @param {'PUBLIC' | 'INTERNAL' | 'PRIVATE'} [visible]
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async listPosts(
|
async listPosts(
|
||||||
category?: Array<string>,
|
|
||||||
contributor?: Array<string>,
|
|
||||||
fieldSelector?: Array<string>,
|
fieldSelector?: Array<string>,
|
||||||
keyword?: string,
|
keyword?: string,
|
||||||
labelSelector?: Array<string>,
|
labelSelector?: Array<string>,
|
||||||
|
@ -734,15 +704,11 @@ export const ApiConsoleHaloRunV1alpha1PostApiFp = function (
|
||||||
publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED",
|
publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED",
|
||||||
size?: number,
|
size?: number,
|
||||||
sort?: Array<string>,
|
sort?: Array<string>,
|
||||||
tag?: Array<string>,
|
|
||||||
visible?: "PUBLIC" | "INTERNAL" | "PRIVATE",
|
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
): Promise<
|
): Promise<
|
||||||
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ListedPostList>
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ListedPostList>
|
||||||
> {
|
> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listPosts(
|
const localVarAxiosArgs = await localVarAxiosParamCreator.listPosts(
|
||||||
category,
|
|
||||||
contributor,
|
|
||||||
fieldSelector,
|
fieldSelector,
|
||||||
keyword,
|
keyword,
|
||||||
labelSelector,
|
labelSelector,
|
||||||
|
@ -750,8 +716,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiFp = function (
|
||||||
publishPhase,
|
publishPhase,
|
||||||
size,
|
size,
|
||||||
sort,
|
sort,
|
||||||
tag,
|
|
||||||
visible,
|
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
return createRequestFunction(
|
return createRequestFunction(
|
||||||
|
@ -954,8 +918,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiFactory = function (
|
||||||
): AxiosPromise<ListedPostList> {
|
): AxiosPromise<ListedPostList> {
|
||||||
return localVarFp
|
return localVarFp
|
||||||
.listPosts(
|
.listPosts(
|
||||||
requestParameters.category,
|
|
||||||
requestParameters.contributor,
|
|
||||||
requestParameters.fieldSelector,
|
requestParameters.fieldSelector,
|
||||||
requestParameters.keyword,
|
requestParameters.keyword,
|
||||||
requestParameters.labelSelector,
|
requestParameters.labelSelector,
|
||||||
|
@ -963,8 +925,6 @@ export const ApiConsoleHaloRunV1alpha1PostApiFactory = function (
|
||||||
requestParameters.publishPhase,
|
requestParameters.publishPhase,
|
||||||
requestParameters.size,
|
requestParameters.size,
|
||||||
requestParameters.sort,
|
requestParameters.sort,
|
||||||
requestParameters.tag,
|
|
||||||
requestParameters.visible,
|
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
.then((request) => request(axios, basePath));
|
.then((request) => request(axios, basePath));
|
||||||
|
@ -1102,20 +1062,6 @@ export interface ApiConsoleHaloRunV1alpha1PostApiFetchPostReleaseContentRequest
|
||||||
* @interface ApiConsoleHaloRunV1alpha1PostApiListPostsRequest
|
* @interface ApiConsoleHaloRunV1alpha1PostApiListPostsRequest
|
||||||
*/
|
*/
|
||||||
export interface ApiConsoleHaloRunV1alpha1PostApiListPostsRequest {
|
export interface ApiConsoleHaloRunV1alpha1PostApiListPostsRequest {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<string>}
|
|
||||||
* @memberof ApiConsoleHaloRunV1alpha1PostApiListPosts
|
|
||||||
*/
|
|
||||||
readonly category?: Array<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<string>}
|
|
||||||
* @memberof ApiConsoleHaloRunV1alpha1PostApiListPosts
|
|
||||||
*/
|
|
||||||
readonly contributor?: Array<string>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field selector for filtering.
|
* Field selector for filtering.
|
||||||
* @type {Array<string>}
|
* @type {Array<string>}
|
||||||
|
@ -1164,20 +1110,6 @@ export interface ApiConsoleHaloRunV1alpha1PostApiListPostsRequest {
|
||||||
* @memberof ApiConsoleHaloRunV1alpha1PostApiListPosts
|
* @memberof ApiConsoleHaloRunV1alpha1PostApiListPosts
|
||||||
*/
|
*/
|
||||||
readonly sort?: Array<string>;
|
readonly sort?: Array<string>;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<string>}
|
|
||||||
* @memberof ApiConsoleHaloRunV1alpha1PostApiListPosts
|
|
||||||
*/
|
|
||||||
readonly tag?: Array<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {'PUBLIC' | 'INTERNAL' | 'PRIVATE'}
|
|
||||||
* @memberof ApiConsoleHaloRunV1alpha1PostApiListPosts
|
|
||||||
*/
|
|
||||||
readonly visible?: "PUBLIC" | "INTERNAL" | "PRIVATE";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1339,8 +1271,6 @@ export class ApiConsoleHaloRunV1alpha1PostApi extends BaseAPI {
|
||||||
) {
|
) {
|
||||||
return ApiConsoleHaloRunV1alpha1PostApiFp(this.configuration)
|
return ApiConsoleHaloRunV1alpha1PostApiFp(this.configuration)
|
||||||
.listPosts(
|
.listPosts(
|
||||||
requestParameters.category,
|
|
||||||
requestParameters.contributor,
|
|
||||||
requestParameters.fieldSelector,
|
requestParameters.fieldSelector,
|
||||||
requestParameters.keyword,
|
requestParameters.keyword,
|
||||||
requestParameters.labelSelector,
|
requestParameters.labelSelector,
|
||||||
|
@ -1348,8 +1278,6 @@ export class ApiConsoleHaloRunV1alpha1PostApi extends BaseAPI {
|
||||||
requestParameters.publishPhase,
|
requestParameters.publishPhase,
|
||||||
requestParameters.size,
|
requestParameters.size,
|
||||||
requestParameters.sort,
|
requestParameters.sort,
|
||||||
requestParameters.tag,
|
|
||||||
requestParameters.visible,
|
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
.then((request) => request(this.axios, this.basePath));
|
.then((request) => request(this.axios, this.basePath));
|
||||||
|
|
|
@ -222,8 +222,6 @@ export const UcApiContentHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* List posts owned by the current user.
|
* List posts owned by the current user.
|
||||||
* @param {Array<string>} [category]
|
|
||||||
* @param {Array<string>} [contributor]
|
|
||||||
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
||||||
* @param {string} [keyword] Posts filtered by keyword.
|
* @param {string} [keyword] Posts filtered by keyword.
|
||||||
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
||||||
|
@ -231,14 +229,10 @@ export const UcApiContentHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
* @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase]
|
* @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase]
|
||||||
* @param {number} [size] Size of one page. Zero indicates no limit.
|
* @param {number} [size] Size of one page. Zero indicates no limit.
|
||||||
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
|
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
|
||||||
* @param {Array<string>} [tag]
|
|
||||||
* @param {'PUBLIC' | 'INTERNAL' | 'PRIVATE'} [visible]
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
listMyPosts: async (
|
listMyPosts: async (
|
||||||
category?: Array<string>,
|
|
||||||
contributor?: Array<string>,
|
|
||||||
fieldSelector?: Array<string>,
|
fieldSelector?: Array<string>,
|
||||||
keyword?: string,
|
keyword?: string,
|
||||||
labelSelector?: Array<string>,
|
labelSelector?: Array<string>,
|
||||||
|
@ -246,8 +240,6 @@ export const UcApiContentHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED",
|
publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED",
|
||||||
size?: number,
|
size?: number,
|
||||||
sort?: Array<string>,
|
sort?: Array<string>,
|
||||||
tag?: Array<string>,
|
|
||||||
visible?: "PUBLIC" | "INTERNAL" | "PRIVATE",
|
|
||||||
options: AxiosRequestConfig = {}
|
options: AxiosRequestConfig = {}
|
||||||
): Promise<RequestArgs> => {
|
): Promise<RequestArgs> => {
|
||||||
const localVarPath = `/apis/uc.api.content.halo.run/v1alpha1/posts`;
|
const localVarPath = `/apis/uc.api.content.halo.run/v1alpha1/posts`;
|
||||||
|
@ -274,14 +266,6 @@ export const UcApiContentHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
// http bearer authentication required
|
// http bearer authentication required
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration);
|
await setBearerAuthToObject(localVarHeaderParameter, configuration);
|
||||||
|
|
||||||
if (category) {
|
|
||||||
localVarQueryParameter["category"] = Array.from(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contributor) {
|
|
||||||
localVarQueryParameter["contributor"] = Array.from(contributor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldSelector) {
|
if (fieldSelector) {
|
||||||
localVarQueryParameter["fieldSelector"] = fieldSelector;
|
localVarQueryParameter["fieldSelector"] = fieldSelector;
|
||||||
}
|
}
|
||||||
|
@ -310,14 +294,6 @@ export const UcApiContentHaloRunV1alpha1PostApiAxiosParamCreator = function (
|
||||||
localVarQueryParameter["sort"] = Array.from(sort);
|
localVarQueryParameter["sort"] = Array.from(sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag) {
|
|
||||||
localVarQueryParameter["tag"] = Array.from(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visible !== undefined) {
|
|
||||||
localVarQueryParameter["visible"] = visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions =
|
let headersFromBaseOptions =
|
||||||
baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
@ -653,8 +629,6 @@ export const UcApiContentHaloRunV1alpha1PostApiFp = function (
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* List posts owned by the current user.
|
* List posts owned by the current user.
|
||||||
* @param {Array<string>} [category]
|
|
||||||
* @param {Array<string>} [contributor]
|
|
||||||
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
||||||
* @param {string} [keyword] Posts filtered by keyword.
|
* @param {string} [keyword] Posts filtered by keyword.
|
||||||
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
||||||
|
@ -662,14 +636,10 @@ export const UcApiContentHaloRunV1alpha1PostApiFp = function (
|
||||||
* @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase]
|
* @param {'DRAFT' | 'PENDING_APPROVAL' | 'PUBLISHED' | 'FAILED'} [publishPhase]
|
||||||
* @param {number} [size] Size of one page. Zero indicates no limit.
|
* @param {number} [size] Size of one page. Zero indicates no limit.
|
||||||
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
|
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp,publishTime
|
||||||
* @param {Array<string>} [tag]
|
|
||||||
* @param {'PUBLIC' | 'INTERNAL' | 'PRIVATE'} [visible]
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async listMyPosts(
|
async listMyPosts(
|
||||||
category?: Array<string>,
|
|
||||||
contributor?: Array<string>,
|
|
||||||
fieldSelector?: Array<string>,
|
fieldSelector?: Array<string>,
|
||||||
keyword?: string,
|
keyword?: string,
|
||||||
labelSelector?: Array<string>,
|
labelSelector?: Array<string>,
|
||||||
|
@ -677,15 +647,11 @@ export const UcApiContentHaloRunV1alpha1PostApiFp = function (
|
||||||
publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED",
|
publishPhase?: "DRAFT" | "PENDING_APPROVAL" | "PUBLISHED" | "FAILED",
|
||||||
size?: number,
|
size?: number,
|
||||||
sort?: Array<string>,
|
sort?: Array<string>,
|
||||||
tag?: Array<string>,
|
|
||||||
visible?: "PUBLIC" | "INTERNAL" | "PRIVATE",
|
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
): Promise<
|
): Promise<
|
||||||
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ListedPostList>
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ListedPostList>
|
||||||
> {
|
> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listMyPosts(
|
const localVarAxiosArgs = await localVarAxiosParamCreator.listMyPosts(
|
||||||
category,
|
|
||||||
contributor,
|
|
||||||
fieldSelector,
|
fieldSelector,
|
||||||
keyword,
|
keyword,
|
||||||
labelSelector,
|
labelSelector,
|
||||||
|
@ -693,8 +659,6 @@ export const UcApiContentHaloRunV1alpha1PostApiFp = function (
|
||||||
publishPhase,
|
publishPhase,
|
||||||
size,
|
size,
|
||||||
sort,
|
sort,
|
||||||
tag,
|
|
||||||
visible,
|
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
return createRequestFunction(
|
return createRequestFunction(
|
||||||
|
@ -875,8 +839,6 @@ export const UcApiContentHaloRunV1alpha1PostApiFactory = function (
|
||||||
): AxiosPromise<ListedPostList> {
|
): AxiosPromise<ListedPostList> {
|
||||||
return localVarFp
|
return localVarFp
|
||||||
.listMyPosts(
|
.listMyPosts(
|
||||||
requestParameters.category,
|
|
||||||
requestParameters.contributor,
|
|
||||||
requestParameters.fieldSelector,
|
requestParameters.fieldSelector,
|
||||||
requestParameters.keyword,
|
requestParameters.keyword,
|
||||||
requestParameters.labelSelector,
|
requestParameters.labelSelector,
|
||||||
|
@ -884,8 +846,6 @@ export const UcApiContentHaloRunV1alpha1PostApiFactory = function (
|
||||||
requestParameters.publishPhase,
|
requestParameters.publishPhase,
|
||||||
requestParameters.size,
|
requestParameters.size,
|
||||||
requestParameters.sort,
|
requestParameters.sort,
|
||||||
requestParameters.tag,
|
|
||||||
requestParameters.visible,
|
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
.then((request) => request(axios, basePath));
|
.then((request) => request(axios, basePath));
|
||||||
|
@ -1008,20 +968,6 @@ export interface UcApiContentHaloRunV1alpha1PostApiGetMyPostDraftRequest {
|
||||||
* @interface UcApiContentHaloRunV1alpha1PostApiListMyPostsRequest
|
* @interface UcApiContentHaloRunV1alpha1PostApiListMyPostsRequest
|
||||||
*/
|
*/
|
||||||
export interface UcApiContentHaloRunV1alpha1PostApiListMyPostsRequest {
|
export interface UcApiContentHaloRunV1alpha1PostApiListMyPostsRequest {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<string>}
|
|
||||||
* @memberof UcApiContentHaloRunV1alpha1PostApiListMyPosts
|
|
||||||
*/
|
|
||||||
readonly category?: Array<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<string>}
|
|
||||||
* @memberof UcApiContentHaloRunV1alpha1PostApiListMyPosts
|
|
||||||
*/
|
|
||||||
readonly contributor?: Array<string>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field selector for filtering.
|
* Field selector for filtering.
|
||||||
* @type {Array<string>}
|
* @type {Array<string>}
|
||||||
|
@ -1070,20 +1016,6 @@ export interface UcApiContentHaloRunV1alpha1PostApiListMyPostsRequest {
|
||||||
* @memberof UcApiContentHaloRunV1alpha1PostApiListMyPosts
|
* @memberof UcApiContentHaloRunV1alpha1PostApiListMyPosts
|
||||||
*/
|
*/
|
||||||
readonly sort?: Array<string>;
|
readonly sort?: Array<string>;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<string>}
|
|
||||||
* @memberof UcApiContentHaloRunV1alpha1PostApiListMyPosts
|
|
||||||
*/
|
|
||||||
readonly tag?: Array<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {'PUBLIC' | 'INTERNAL' | 'PRIVATE'}
|
|
||||||
* @memberof UcApiContentHaloRunV1alpha1PostApiListMyPosts
|
|
||||||
*/
|
|
||||||
readonly visible?: "PUBLIC" | "INTERNAL" | "PRIVATE";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1228,8 +1160,6 @@ export class UcApiContentHaloRunV1alpha1PostApi extends BaseAPI {
|
||||||
) {
|
) {
|
||||||
return UcApiContentHaloRunV1alpha1PostApiFp(this.configuration)
|
return UcApiContentHaloRunV1alpha1PostApiFp(this.configuration)
|
||||||
.listMyPosts(
|
.listMyPosts(
|
||||||
requestParameters.category,
|
|
||||||
requestParameters.contributor,
|
|
||||||
requestParameters.fieldSelector,
|
requestParameters.fieldSelector,
|
||||||
requestParameters.keyword,
|
requestParameters.keyword,
|
||||||
requestParameters.labelSelector,
|
requestParameters.labelSelector,
|
||||||
|
@ -1237,8 +1167,6 @@ export class UcApiContentHaloRunV1alpha1PostApi extends BaseAPI {
|
||||||
requestParameters.publishPhase,
|
requestParameters.publishPhase,
|
||||||
requestParameters.size,
|
requestParameters.size,
|
||||||
requestParameters.sort,
|
requestParameters.sort,
|
||||||
requestParameters.tag,
|
|
||||||
requestParameters.visible,
|
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
.then((request) => request(this.axios, this.basePath));
|
.then((request) => request(this.axios, this.basePath));
|
||||||
|
|
|
@ -34,6 +34,12 @@ export interface PluginStatus {
|
||||||
* @memberof PluginStatus
|
* @memberof PluginStatus
|
||||||
*/
|
*/
|
||||||
entry?: string;
|
entry?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PluginStatus
|
||||||
|
*/
|
||||||
|
lastProbeState?: PluginStatusLastProbeStateEnum;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
@ -66,7 +72,7 @@ export interface PluginStatus {
|
||||||
stylesheet?: string;
|
stylesheet?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PluginStatusPhaseEnum = {
|
export const PluginStatusLastProbeStateEnum = {
|
||||||
Created: "CREATED",
|
Created: "CREATED",
|
||||||
Disabled: "DISABLED",
|
Disabled: "DISABLED",
|
||||||
Resolved: "RESOLVED",
|
Resolved: "RESOLVED",
|
||||||
|
@ -75,5 +81,19 @@ export const PluginStatusPhaseEnum = {
|
||||||
Failed: "FAILED",
|
Failed: "FAILED",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export type PluginStatusLastProbeStateEnum =
|
||||||
|
(typeof PluginStatusLastProbeStateEnum)[keyof typeof PluginStatusLastProbeStateEnum];
|
||||||
|
export const PluginStatusPhaseEnum = {
|
||||||
|
Pending: "PENDING",
|
||||||
|
Starting: "STARTING",
|
||||||
|
Created: "CREATED",
|
||||||
|
Disabled: "DISABLED",
|
||||||
|
Resolved: "RESOLVED",
|
||||||
|
Started: "STARTED",
|
||||||
|
Stopped: "STOPPED",
|
||||||
|
Failed: "FAILED",
|
||||||
|
Unknown: "UNKNOWN",
|
||||||
|
} as const;
|
||||||
|
|
||||||
export type PluginStatusPhaseEnum =
|
export type PluginStatusPhaseEnum =
|
||||||
(typeof PluginStatusPhaseEnum)[keyof typeof PluginStatusPhaseEnum];
|
(typeof PluginStatusPhaseEnum)[keyof typeof PluginStatusPhaseEnum];
|
||||||
|
|
|
@ -149,7 +149,9 @@ const handleBuildSearchIndex = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
apiClient.extension.category
|
apiClient.extension.category
|
||||||
.listcontentHaloRunV1alpha1Category()
|
.listcontentHaloRunV1alpha1Category({
|
||||||
|
sort: ["metadata.creationTimestamp,desc"],
|
||||||
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
response.data.items.forEach((category) => {
|
response.data.items.forEach((category) => {
|
||||||
fuse.add({
|
fuse.add({
|
||||||
|
@ -168,23 +170,27 @@ const handleBuildSearchIndex = () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
apiClient.extension.tag.listcontentHaloRunV1alpha1Tag().then((response) => {
|
apiClient.extension.tag
|
||||||
response.data.items.forEach((tag) => {
|
.listcontentHaloRunV1alpha1Tag({
|
||||||
fuse.add({
|
sort: ["metadata.creationTimestamp,desc"],
|
||||||
title: tag.spec.displayName,
|
})
|
||||||
icon: {
|
.then((response) => {
|
||||||
component: markRaw(IconBookRead),
|
response.data.items.forEach((tag) => {
|
||||||
},
|
fuse.add({
|
||||||
group: t("core.components.global_search.groups.tag"),
|
title: tag.spec.displayName,
|
||||||
route: {
|
icon: {
|
||||||
name: "Tags",
|
component: markRaw(IconBookRead),
|
||||||
query: {
|
|
||||||
name: tag.metadata.name,
|
|
||||||
},
|
},
|
||||||
},
|
group: t("core.components.global_search.groups.tag"),
|
||||||
|
route: {
|
||||||
|
name: "Tags",
|
||||||
|
query: {
|
||||||
|
name: tag.metadata.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUserHasPermission(["system:singlepages:view"])) {
|
if (currentUserHasPermission(["system:singlepages:view"])) {
|
||||||
|
|
|
@ -15,7 +15,9 @@ declare module "@formkit/inputs" {
|
||||||
function optionsHandler(node: FormKitNode) {
|
function optionsHandler(node: FormKitNode) {
|
||||||
node.on("created", async () => {
|
node.on("created", async () => {
|
||||||
const { data } =
|
const { data } =
|
||||||
await apiClient.extension.category.listcontentHaloRunV1alpha1Category();
|
await apiClient.extension.category.listcontentHaloRunV1alpha1Category({
|
||||||
|
sort: ["metadata.creationTimestamp,desc"],
|
||||||
|
});
|
||||||
|
|
||||||
node.props.options = data.items.map((category) => {
|
node.props.options = data.items.map((category) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -15,7 +15,9 @@ declare module "@formkit/inputs" {
|
||||||
function optionsHandler(node: FormKitNode) {
|
function optionsHandler(node: FormKitNode) {
|
||||||
node.on("created", async () => {
|
node.on("created", async () => {
|
||||||
const { data } =
|
const { data } =
|
||||||
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag();
|
await apiClient.extension.tag.listcontentHaloRunV1alpha1Tag({
|
||||||
|
sort: ["metadata.creationTimestamp,desc"],
|
||||||
|
});
|
||||||
|
|
||||||
node.props.options = data.items.map((tag) => {
|
node.props.options = data.items.map((tag) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -69,7 +69,7 @@ const {
|
||||||
size.value = data.size;
|
size.value = data.size;
|
||||||
},
|
},
|
||||||
refetchInterval: (data) => {
|
refetchInterval: (data) => {
|
||||||
const abnormalPosts = data?.items.filter((post) => {
|
const hasAbnormalPost = data?.items.some((post) => {
|
||||||
const { spec, metadata, status } = post.post;
|
const { spec, metadata, status } = post.post;
|
||||||
return (
|
return (
|
||||||
spec.deleted ||
|
spec.deleted ||
|
||||||
|
@ -78,7 +78,7 @@ const {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return abnormalPosts?.length ? 1000 : false;
|
return hasAbnormalPost ? 1000 : false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue