Skip to content

Commit

Permalink
#12 add pagination for bikes
Browse files Browse the repository at this point in the history
  • Loading branch information
bthreader committed Dec 21, 2023
1 parent 2bfd4f6 commit c9f6e8f
Show file tree
Hide file tree
Showing 41 changed files with 532 additions and 104 deletions.
5 changes: 4 additions & 1 deletion service-course-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@ dependencies {
testImplementation "org.mockito:mockito-core:3.+"
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
}

generateJava {
schemaPaths = ["${projectDir}/src/main/resources/schema"] // List of directories containing schema files
packageName = 'servicecourse.generated' // The package name to use to generate sources
generateClient = true // Enable generating the type safe query API
typeMapping = [
"Url": "java.net.URL",
"Url" : "java.net.URL",
"PositiveInt": "java.lang.Integer"
]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.netflix.graphql.dgs.*;
import lombok.RequiredArgsConstructor;
import org.dataloader.DataLoader;
import servicecourse.generated.DgsConstants;
import servicecourse.generated.types.BikeBrand;
import servicecourse.generated.types.CreateBikeBrandInput;
import servicecourse.generated.types.Model;
Expand All @@ -23,7 +24,7 @@ public class BikeBrandsDataFetcher {
* A data loader is used to avoid sending multiple separate requests to the models service when
* handling a request that involves multiple bike brands (see {@link #bikeBrands()}).
*/
@DgsData(parentType = "BikeBrand", field = "models")
@DgsData(parentType = DgsConstants.BIKEBRAND.TYPE_NAME, field = DgsConstants.BIKEBRAND.Models)
public CompletableFuture<List<Model>> models(DgsDataFetchingEnvironment dfe) {
BikeBrand bikeBrand = dfe.getSource();
DataLoader<String, List<Model>> modelsDataLoader = dfe.getDataLoader("models");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,18 @@
import com.netflix.graphql.dgs.DgsQuery;
import com.netflix.graphql.dgs.InputArgument;
import lombok.RequiredArgsConstructor;
import servicecourse.generated.types.Bike;
import servicecourse.generated.types.BikesFilterInput;
import servicecourse.generated.types.CreateBikeInput;
import servicecourse.generated.types.UpdateBikeInput;
import servicecourse.generated.types.*;
import servicecourse.services.bikes.BikesService;

import java.util.List;

@DgsComponent
@RequiredArgsConstructor
public class BikesDataFetcher {
private final BikesService bikesService;

@DgsQuery
public List<Bike> bikes(@InputArgument BikesFilterInput filter) {
return bikesService.bikes(filter);
public BikeConnection bikes(@InputArgument BikesFilterInput filter, @InputArgument int first,
@InputArgument CursorInput after) {
return bikesService.bikes(filter, first, after);
}

@DgsMutation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.netflix.graphql.dgs.DgsDataLoader;
import lombok.RequiredArgsConstructor;
import org.dataloader.MappedBatchLoader;
import servicecourse.generated.DgsConstants;
import servicecourse.generated.types.Model;
import servicecourse.services.models.ModelsService;

Expand All @@ -13,7 +14,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

@DgsDataLoader(name = "models")
@DgsDataLoader(name = DgsConstants.BIKEBRAND.Models)
@RequiredArgsConstructor
public class ModelsDataLoader implements MappedBatchLoader<String, List<Model>> {
private final ModelsService modelsService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import jakarta.persistence.*;
import lombok.*;
import servicecourse.generated.types.Bike;
import servicecourse.generated.types.BikeConnectionEdge;
import servicecourse.repo.common.URLConverter;
import servicecourse.services.bikes.BikeId;
import servicecourse.services.bikes.UpdateBikeParams;
import servicecourse.utils.Base64ToLongConverter;

import java.net.URL;

Expand Down Expand Up @@ -57,11 +59,22 @@ public void apply(UpdateBikeParams input) {
input.heroImageUrl().ifPresent(this::setHeroImageUrl);
}

public BikeConnectionEdge asBikeConnectionEdge() {
return BikeConnectionEdge.newBuilder()
.node(this.asBike())
.cursor(this.base64Id())
.build();
}

public String base64Id() {
return Base64ToLongConverter.encodeToBase64(id);
}

private void setGroupset(GroupsetEntity groupset) {
this.groupset = groupset;
}

private void setHeroImageUrl(URL heroImageUrl) {
private void setHeroImageUrl(@Nullable URL heroImageUrl) {
this.heroImageUrl = heroImageUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@

@StaticMetamodel(BikeEntity.class)
public class BikeEntity_ {
public static volatile SingularAttribute<BikeEntity, Long> id;
public static volatile SingularAttribute<BikeEntity, String> size;
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package servicecourse.repo;

import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface BikeRepository extends JpaRepository<BikeEntity, Long>, JpaSpecificationExecutor<BikeEntity> {
@NonNull
@Query("SELECT b FROM BikeEntity b JOIN FETCH b.model JOIN FETCH b.groupset")
List<BikeEntity> findAll();
Page<BikeEntity> findAll(@NotNull Specification<BikeEntity> spec, @NotNull Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class GroupsetEntitySpecification {
Expand All @@ -27,7 +26,7 @@ public static Specification<GroupsetEntity> from(GroupsetFilterInput input) {
Optional.ofNullable(input.getIsElectronic())
.map(GroupsetEntitySpecification::isElectronic))
.flatMap(Optional::stream)
.collect(Collectors.toList());
.toList();

return specifications.isEmpty() ? SpecificationUtils.matchAll()
: Specification.allOf(specifications);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ModelEntitySpecification {
Expand All @@ -28,7 +27,7 @@ public static Specification<ModelEntity> from(ModelFilterInput input) {
Optional.ofNullable(input.getBrandName())
.map(ModelEntitySpecification::brandName))
.flatMap(Optional::stream)
.collect(Collectors.toList());
.toList();

return specifications.isEmpty() ? SpecificationUtils.matchAll()
: Specification.allOf(specifications);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package servicecourse.repo.common;

import lombok.experimental.UtilityClass;

@UtilityClass
public class EntityConstants {
public static final int MINIMUM_ID_VALUE = 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class IntegerFilterSpecification {
Expand All @@ -30,7 +29,7 @@ public static <T> Specification<T> from(@NonNull IntegerFilterInput input,
equalsSpecification(input.getEquals(), fieldExpression),
inSpecification(input.getIn(), fieldExpression))
.flatMap(Optional::stream)
.collect(Collectors.toList());
.toList();

return specifications.isEmpty() ? SpecificationUtils.alwaysTruePredicate(cb)
: Specification.anyOf(specifications).toPredicate(root, query, cb);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package servicecourse.repo.common;

import jakarta.persistence.criteria.Expression;
import jakarta.persistence.metamodel.SingularAttribute;
import org.springframework.data.jpa.domain.Specification;

public class LongFilterSpecification {
public static <T> Specification<T> newGreaterThanSpecification(Long greaterThan,
SingularAttribute<T, Long> fieldPath) {
return (root, query, cb) -> {
Expression<Long> fieldExpression = root.get(fieldPath);
return cb.greaterThan(fieldExpression, greaterThan);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package servicecourse.repo.common;

import lombok.experimental.UtilityClass;
import org.springframework.data.domain.Sort;

@UtilityClass
public class SortUtils {
public static Sort sortByIdAsc() {
return Sort.by("id").ascending();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StringFilterSpecification {
Expand All @@ -31,7 +30,7 @@ public static <T> Specification<T> from(@NonNull StringFilterInput input,
containsSpecification(input.getContains(), fieldExpression),
inSpecification(input.getIn(), fieldExpression))
.flatMap(Optional::stream)
.collect(Collectors.toList());
.toList();

return specifications.isEmpty() ? SpecificationUtils.alwaysTruePredicate(cb)
: Specification.anyOf(specifications).toPredicate(root, query, cb);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import servicecourse.generated.types.BikeBrand;
import servicecourse.generated.types.CreateBikeBrandInput;
import servicecourse.services.exceptions.BikeBrandAlreadyExistsException;
import servicecourse.services.exceptions.BikeBrandNotFoundException;
import servicecourse.services.common.exceptions.BikeBrandAlreadyExistsException;
import servicecourse.services.common.exceptions.BikeBrandNotFoundException;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
import servicecourse.generated.types.CreateBikeBrandInput;
import servicecourse.repo.BikeBrandEntity;
import servicecourse.repo.BikeBrandRepository;
import servicecourse.services.exceptions.BikeBrandAlreadyExistsException;
import servicecourse.services.exceptions.BikeBrandNotFoundException;
import servicecourse.services.common.exceptions.BikeBrandAlreadyExistsException;
import servicecourse.services.common.exceptions.BikeBrandNotFoundException;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -51,6 +50,6 @@ public List<BikeBrand> bikeBrands() {
return bikeBrandRepository.findAll()
.stream()
.map(BikeBrandEntity::asBikeBrand)
.collect(Collectors.toList());
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package servicecourse.services.bikes;

import servicecourse.generated.types.Bike;
import servicecourse.generated.types.BikesFilterInput;
import servicecourse.generated.types.CreateBikeInput;
import servicecourse.generated.types.UpdateBikeInput;
import servicecourse.services.exceptions.BikeNotFoundException;
import servicecourse.services.exceptions.GroupsetNotFoundException;
import servicecourse.services.exceptions.ModelNotFoundException;

import java.util.List;
import org.springframework.lang.Nullable;
import servicecourse.generated.types.*;
import servicecourse.services.common.exceptions.BikeNotFoundException;
import servicecourse.services.common.exceptions.GroupsetNotFoundException;
import servicecourse.services.common.exceptions.ModelNotFoundException;

public interface BikesService {
List<Bike> bikes(BikesFilterInput filter);
/**
* @param filter the filter to apply to the bikes
* @param first the number of results to return. Must be greater than zero. The maximum value
* is 100, values higher than this will be truncated.
* @param after if specified, only return results after the cursor
* @return a connection of bikes
* @throws IllegalArgumentException if first is less than one
*/
BikeConnection bikes(@Nullable BikesFilterInput filter, int first, @Nullable CursorInput after);

/**
* @param input the details of the new bike
Expand Down
Loading

0 comments on commit c9f6e8f

Please sign in to comment.