diff --git a/src/main/java/run/halo/app/core/extension/attachment/endpoint/AttachmentEndpoint.java b/src/main/java/run/halo/app/core/extension/attachment/endpoint/AttachmentEndpoint.java index 134dff9a0..2d0597b22 100644 --- a/src/main/java/run/halo/app/core/extension/attachment/endpoint/AttachmentEndpoint.java +++ b/src/main/java/run/halo/app/core/extension/attachment/endpoint/AttachmentEndpoint.java @@ -1,5 +1,6 @@ package run.halo.app.core.extension.attachment.endpoint; +import static java.util.Comparator.comparing; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; @@ -9,12 +10,17 @@ import static run.halo.app.extension.ListResult.generateGenericClass; import static run.halo.app.extension.router.QueryParamBuildUtil.buildParametersFromType; import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate; +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; import lombok.extern.slf4j.Slf4j; import org.springdoc.core.fn.builders.requestbody.Builder; import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.FilePart; @@ -30,6 +36,7 @@ import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -37,6 +44,8 @@ import run.halo.app.core.extension.attachment.Attachment; import run.halo.app.core.extension.attachment.Policy; import run.halo.app.core.extension.attachment.endpoint.AttachmentHandler.UploadOption; import run.halo.app.core.extension.endpoint.CustomEndpoint; +import run.halo.app.core.extension.endpoint.SortResolver; +import run.halo.app.extension.Comparators; import run.halo.app.extension.ConfigMap; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Ref; @@ -139,8 +148,9 @@ public class AttachmentEndpoint implements CustomEndpoint { } Mono search(ServerRequest request) { - var searchRequest = new SearchRequest(request.queryParams()); - return client.list(Attachment.class, searchRequest.toPredicate(), null, + var searchRequest = new SearchRequest(request); + return client.list(Attachment.class, + searchRequest.toPredicate(), searchRequest.toComparator(), searchRequest.getPage(), searchRequest.getSize()) .flatMap(listResult -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) @@ -161,12 +171,24 @@ public class AttachmentEndpoint implements CustomEndpoint { @Schema(description = "Name of user who uploaded the attachment") Optional getUploadedBy(); + @ArraySchema(uniqueItems = true, + arraySchema = @Schema(name = "sort", + description = "Sort property and direction of the list result. Supported fields: " + + "creationTimestamp, size"), + schema = @Schema(description = "like field,asc or field,desc", + implementation = String.class, + example = "creationTimestamp,desc")) + Sort getSort(); + } public static class SearchRequest extends QueryListRequest implements ISearchRequest { - public SearchRequest(MultiValueMap queryParams) { - super(queryParams); + private final ServerWebExchange exchange; + + public SearchRequest(ServerRequest request) { + super(request.queryParams()); + this.exchange = request.exchange(); } @Override @@ -193,6 +215,11 @@ public class AttachmentEndpoint implements CustomEndpoint { .filter(StringUtils::hasText); } + @Override + public Sort getSort() { + return SortResolver.defaultInstance.resolve(exchange); + } + public Predicate toPredicate() { var predicate = (Predicate) (attachment) -> getDisplayName() .map(displayNameInParam -> { @@ -222,6 +249,37 @@ public class AttachmentEndpoint implements CustomEndpoint { return predicate.and(selectorPredicate); } + + public Comparator toComparator() { + var sort = getSort(); + List> comparators = new ArrayList<>(); + var creationOrder = sort.getOrderFor("creationTimestamp"); + if (creationOrder != null) { + Comparator comparator = comparing( + attachment -> attachment.getMetadata().getCreationTimestamp()); + if (creationOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + + var sizeOrder = sort.getOrderFor("size"); + if (sizeOrder != null) { + Comparator comparator = + comparing(attachment -> attachment.getSpec().getSize()); + if (sizeOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + + // add default comparator + comparators.add(Comparators.compareCreationTimestamp(false)); + comparators.add(Comparators.compareName(true)); + return comparators.stream() + .reduce(Comparator::thenComparing) + .orElse(null); + } } public interface IUploadRequest { diff --git a/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java b/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java index e19636514..96e1f3d51 100644 --- a/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java +++ b/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java @@ -22,7 +22,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; +import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; @@ -47,6 +49,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import reactor.util.retry.Retry; import run.halo.app.core.extension.Plugin; +import run.halo.app.extension.Comparators; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.router.IListRequest.QueryListRequest; import run.halo.app.infra.utils.FileUtils; @@ -243,14 +246,20 @@ public class PluginEndpoint implements CustomEndpoint { public Comparator toComparator() { var sort = getSort(); var ctOrder = sort.getOrderFor("creationTimestamp"); - Comparator comparator = null; + List> comparators = new ArrayList<>(); if (ctOrder != null) { - comparator = comparing(plugin -> plugin.getMetadata().getCreationTimestamp()); + Comparator comparator = + comparing(plugin -> plugin.getMetadata().getCreationTimestamp()); if (ctOrder.isDescending()) { comparator = comparator.reversed(); } + comparators.add(comparator); } - return comparator; + comparators.add(Comparators.compareCreationTimestamp(false)); + comparators.add(Comparators.compareName(true)); + return comparators.stream() + .reduce(Comparator::thenComparing) + .orElse(null); } } diff --git a/src/main/java/run/halo/app/extension/Comparators.java b/src/main/java/run/halo/app/extension/Comparators.java new file mode 100644 index 000000000..779840324 --- /dev/null +++ b/src/main/java/run/halo/app/extension/Comparators.java @@ -0,0 +1,20 @@ +package run.halo.app.extension; + +import java.time.Instant; +import java.util.Comparator; + +public enum Comparators { + ; + + public static Comparator compareCreationTimestamp(boolean asc) { + var comparator = + Comparator.comparing(e -> e.getMetadata().getCreationTimestamp()); + return asc ? comparator : comparator.reversed(); + } + + public static Comparator compareName(boolean asc) { + var comparator = Comparator.comparing(e -> e.getMetadata().getName()); + return asc ? comparator : comparator.reversed(); + } + +} diff --git a/src/test/java/run/halo/app/extension/ComparatorsTest.java b/src/test/java/run/halo/app/extension/ComparatorsTest.java new file mode 100644 index 000000000..82657213c --- /dev/null +++ b/src/test/java/run/halo/app/extension/ComparatorsTest.java @@ -0,0 +1,97 @@ +package run.halo.app.extension; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ComparatorsTest { + + @Nested + class CompareCreationTimestamp { + + FakeExtension createFake(String name, Instant creationTimestamp) { + var metadata = new Metadata(); + metadata.setName(name); + metadata.setCreationTimestamp(creationTimestamp); + var fake = new FakeExtension(); + fake.setMetadata(metadata); + return fake; + } + + @Test + void desc() { + var comparator = Comparators.compareCreationTimestamp(false); + var now = Instant.now(); + var before = now.minusMillis(1); + var after = now.plusMillis(1); + + var fakeNow = createFake("now", now); + var fakeBefore = createFake("before", before); + var fakeAfter = createFake("after", after); + + var sortedFakes = new ArrayList<>(List.of(fakeNow, fakeAfter, fakeBefore)); + sortedFakes.sort(comparator); + + assertEquals(List.of(fakeAfter, fakeNow, fakeBefore), sortedFakes); + } + + @Test + void asc() { + var comparator = Comparators.compareCreationTimestamp(true); + var now = Instant.now(); + var before = now.minusMillis(1); + var after = now.plusMillis(1); + + var fakeNow = createFake("now", now); + var fakeBefore = createFake("before", before); + var fakeAfter = createFake("after", after); + + var sortedFakes = new ArrayList<>(List.of(fakeNow, fakeAfter, fakeBefore)); + sortedFakes.sort(comparator); + + assertEquals(List.of(fakeBefore, fakeNow, fakeAfter), sortedFakes); + } + } + + @Nested + class CompareName { + + FakeExtension createFake(String name) { + var metadata = new Metadata(); + metadata.setName(name); + var fake = new FakeExtension(); + fake.setMetadata(metadata); + return fake; + } + + @Test + void desc() { + var comparator = Comparators.compareName(false); + var fake01 = createFake("fake01"); + var fake02 = createFake("fake02"); + var fake03 = createFake("fake03"); + + var sortedFakes = new ArrayList<>(List.of(fake02, fake01, fake03)); + sortedFakes.sort(comparator); + + assertEquals(List.of(fake03, fake02, fake01), sortedFakes); + } + + @Test + void asc() { + var comparator = Comparators.compareName(true); + var fake01 = createFake("fake01"); + var fake02 = createFake("fake02"); + var fake03 = createFake("fake03"); + + var sortedFakes = new ArrayList<>(List.of(fake02, fake03, fake01)); + sortedFakes.sort(comparator); + + assertEquals(List.of(fake01, fake02, fake03), sortedFakes); + } + } +} \ No newline at end of file