diff --git a/README.md b/README.md
index af2cab8..322fbc1 100644
--- a/README.md
+++ b/README.md
@@ -161,4 +161,5 @@
- [Spring Boot Rest Controller Unit Test with @WebMvcTest](https://www.bezkoder.com/spring-boot-webmvctest/)
- [Redis Commands](https://auth0.com/blog/introduction-to-redis-install-cli-commands-and-data-types/)
- [Running RedisInsight using Docker Compose](https://collabnix.com/running-redisinsight-using-docker-compose/)
-- [Google Play Integrity API](https://developer.android.com/google/play/integrity)
\ No newline at end of file
+- [Google Play Integrity API](https://developer.android.com/google/play/integrity)
+- [A Guide to Querydsl with JPA](https://www.baeldung.com/querydsl-with-jpa-tutorial)
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index c534af9..67219d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,21 @@
spring-data-envers
+
+ com.querydsl
+ querydsl-apt
+ 5.0.0
+ jakarta
+ provided
+
+
+
+ com.querydsl
+ querydsl-jpa
+ 5.0.0
+ jakarta
+
+
org.projectlombok
lombok
@@ -224,6 +239,23 @@
+
+ com.mysema.maven
+ apt-maven-plugin
+ 1.1.3
+
+
+
+ process
+
+
+ target/generated-sources/java
+ com.querydsl.apt.jpa.JPAAnnotationProcessor
+
+
+
+
+
com.spotify
dockerfile-maven-plugin
diff --git a/src/main/java/com/mb/livedataservice/api/controller/TutorialController.java b/src/main/java/com/mb/livedataservice/api/controller/TutorialController.java
index f9a35ba..b740f4b 100644
--- a/src/main/java/com/mb/livedataservice/api/controller/TutorialController.java
+++ b/src/main/java/com/mb/livedataservice/api/controller/TutorialController.java
@@ -1,11 +1,14 @@
package com.mb.livedataservice.api.controller;
+import com.mb.livedataservice.api.filter.ApiTutorialFilter;
import com.mb.livedataservice.api.request.ApiTutorialRequest;
import com.mb.livedataservice.api.request.ApiTutorialUpdateRequest;
import com.mb.livedataservice.api.response.ApiTutorialResponse;
import com.mb.livedataservice.mapper.TutorialMapper;
import com.mb.livedataservice.service.TutorialService;
import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -57,4 +60,9 @@ public ResponseEntity deleteAllTutorials() {
public ResponseEntity> findByPublished() {
return new ResponseEntity<>(tutorialMapper.map(tutorialService.findByPublished(true)), HttpStatus.OK);
}
+
+ @GetMapping("/tutorials/filter")
+ public ResponseEntity> findAll(ApiTutorialFilter apiTutorialFilter, Pageable pageable) {
+ return new ResponseEntity<>(tutorialService.findAll(tutorialMapper.map(apiTutorialFilter), pageable).map(tutorialMapper::map), HttpStatus.OK);
+ }
}
diff --git a/src/main/java/com/mb/livedataservice/api/filter/ApiTutorialFilter.java b/src/main/java/com/mb/livedataservice/api/filter/ApiTutorialFilter.java
new file mode 100644
index 0000000..cb006cd
--- /dev/null
+++ b/src/main/java/com/mb/livedataservice/api/filter/ApiTutorialFilter.java
@@ -0,0 +1,19 @@
+package com.mb.livedataservice.api.filter;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ApiTutorialFilter {
+
+ private String title;
+
+ private String description;
+
+ private boolean published;
+}
diff --git a/src/main/java/com/mb/livedataservice/data/filter/Filter.java b/src/main/java/com/mb/livedataservice/data/filter/Filter.java
new file mode 100644
index 0000000..6f038e5
--- /dev/null
+++ b/src/main/java/com/mb/livedataservice/data/filter/Filter.java
@@ -0,0 +1,8 @@
+package com.mb.livedataservice.data.filter;
+
+import com.querydsl.core.types.Predicate;
+
+public interface Filter {
+
+ Predicate toPredicate();
+}
diff --git a/src/main/java/com/mb/livedataservice/data/filter/TutorialFilter.java b/src/main/java/com/mb/livedataservice/data/filter/TutorialFilter.java
new file mode 100644
index 0000000..cf1ad40
--- /dev/null
+++ b/src/main/java/com/mb/livedataservice/data/filter/TutorialFilter.java
@@ -0,0 +1,39 @@
+package com.mb.livedataservice.data.filter;
+
+import com.mb.livedataservice.data.model.QTutorial;
+import com.querydsl.core.types.Predicate;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TutorialFilter implements Filter {
+
+ private String title;
+
+ private String description;
+
+ private boolean published;
+
+ @Override
+ public Predicate toPredicate() {
+ QTutorial qTutorial = QTutorial.tutorial;
+ BooleanExpression predicate = qTutorial.id.isNotNull();
+
+ if (StringUtils.isNotBlank(title)) {
+ predicate = predicate.and(qTutorial.title.equalsIgnoreCase(title));
+ }
+
+ if (StringUtils.isNotBlank(description)) {
+ predicate = predicate.and(qTutorial.description.equalsIgnoreCase(description));
+ }
+
+ return predicate;
+ }
+}
diff --git a/src/main/java/com/mb/livedataservice/data/repository/ScoreBoardRepository.java b/src/main/java/com/mb/livedataservice/data/repository/ScoreBoardRepository.java
index dc37396..5167aa9 100644
--- a/src/main/java/com/mb/livedataservice/data/repository/ScoreBoardRepository.java
+++ b/src/main/java/com/mb/livedataservice/data/repository/ScoreBoardRepository.java
@@ -5,13 +5,14 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
-public interface ScoreBoardRepository extends JpaRepository {
+public interface ScoreBoardRepository extends JpaRepository, QuerydslPredicateExecutor {
Optional findByHomeTeamNameAndAwayTeamNameAndDeletedIsFalse(String homeTeamName, String awayTeamName);
@@ -20,4 +21,4 @@ public interface ScoreBoardRepository extends JpaRepository {
Optional findByIdAndDeletedIsFalse(Long id);
List findAllByDeletedIsTrue(Sort sort);
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/mb/livedataservice/data/repository/TutorialRepository.java b/src/main/java/com/mb/livedataservice/data/repository/TutorialRepository.java
index b671e84..cd1b991 100644
--- a/src/main/java/com/mb/livedataservice/data/repository/TutorialRepository.java
+++ b/src/main/java/com/mb/livedataservice/data/repository/TutorialRepository.java
@@ -2,10 +2,11 @@
import com.mb.livedataservice.data.model.Tutorial;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import java.util.List;
-public interface TutorialRepository extends JpaRepository {
+public interface TutorialRepository extends JpaRepository, QuerydslPredicateExecutor {
List findByPublished(boolean published);
diff --git a/src/main/java/com/mb/livedataservice/mapper/TutorialMapper.java b/src/main/java/com/mb/livedataservice/mapper/TutorialMapper.java
index bcdfe92..d38435a 100644
--- a/src/main/java/com/mb/livedataservice/mapper/TutorialMapper.java
+++ b/src/main/java/com/mb/livedataservice/mapper/TutorialMapper.java
@@ -1,8 +1,10 @@
package com.mb.livedataservice.mapper;
+import com.mb.livedataservice.api.filter.ApiTutorialFilter;
import com.mb.livedataservice.api.request.ApiTutorialRequest;
import com.mb.livedataservice.api.request.ApiTutorialUpdateRequest;
import com.mb.livedataservice.api.response.ApiTutorialResponse;
+import com.mb.livedataservice.data.filter.TutorialFilter;
import com.mb.livedataservice.data.model.Tutorial;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@@ -21,4 +23,6 @@ public interface TutorialMapper {
ApiTutorialResponse map(Tutorial tutorial);
List map(List tutorial);
+
+ TutorialFilter map(ApiTutorialFilter apiTutorialFilter);
}
diff --git a/src/main/java/com/mb/livedataservice/service/TutorialService.java b/src/main/java/com/mb/livedataservice/service/TutorialService.java
index b2f7524..547962b 100644
--- a/src/main/java/com/mb/livedataservice/service/TutorialService.java
+++ b/src/main/java/com/mb/livedataservice/service/TutorialService.java
@@ -1,6 +1,9 @@
package com.mb.livedataservice.service;
+import com.mb.livedataservice.data.filter.TutorialFilter;
import com.mb.livedataservice.data.model.Tutorial;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import java.util.List;
@@ -19,4 +22,6 @@ public interface TutorialService {
void deleteAll();
List findByPublished(boolean b);
+
+ Page findAll(TutorialFilter filter, Pageable pageable);
}
diff --git a/src/main/java/com/mb/livedataservice/service/impl/TutorialServiceImpl.java b/src/main/java/com/mb/livedataservice/service/impl/TutorialServiceImpl.java
index 079de3d..c095dd9 100644
--- a/src/main/java/com/mb/livedataservice/service/impl/TutorialServiceImpl.java
+++ b/src/main/java/com/mb/livedataservice/service/impl/TutorialServiceImpl.java
@@ -1,5 +1,6 @@
package com.mb.livedataservice.service.impl;
+import com.mb.livedataservice.data.filter.TutorialFilter;
import com.mb.livedataservice.data.model.Tutorial;
import com.mb.livedataservice.data.repository.TutorialRepository;
import com.mb.livedataservice.exception.BaseException;
@@ -8,6 +9,8 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -61,4 +64,9 @@ public void deleteAll() {
public List findByPublished(boolean b) {
return tutorialRepository.findByPublished(b);
}
-}
\ No newline at end of file
+
+ @Override
+ public Page findAll(TutorialFilter filter, Pageable pageable) {
+ return tutorialRepository.findAll(filter.toPredicate(), pageable);
+ }
+}
diff --git a/src/test/java/com/mb/livedataservice/base/BaseUnitTest.java b/src/test/java/com/mb/livedataservice/base/BaseUnitTest.java
index f9b7fc6..c2ad96d 100644
--- a/src/test/java/com/mb/livedataservice/base/BaseUnitTest.java
+++ b/src/test/java/com/mb/livedataservice/base/BaseUnitTest.java
@@ -1,5 +1,6 @@
package com.mb.livedataservice.base;
+import com.mb.livedataservice.api.filter.ApiTutorialFilter;
import com.mb.livedataservice.api.request.ApiScoreBoardRequest;
import com.mb.livedataservice.api.request.ApiScoreBoardUpdateRequest;
import com.mb.livedataservice.api.request.ApiTutorialRequest;
@@ -139,4 +140,8 @@ public Tutorial getUpdatedTutorial() {
public ApiTutorialResponse getUpdatedApiTutorialResponse() {
return new ApiTutorialResponse(1, "Updated", "Updated", true);
}
+
+ public ApiTutorialFilter getApiTutorialFilter() {
+ return new ApiTutorialFilter("Updated", "Updated", true);
+ }
}
diff --git a/src/test/java/com/mb/livedataservice/helper/RestResponsePage.java b/src/test/java/com/mb/livedataservice/helper/RestResponsePage.java
new file mode 100644
index 0000000..01f7955
--- /dev/null
+++ b/src/test/java/com/mb/livedataservice/helper/RestResponsePage.java
@@ -0,0 +1,42 @@
+package com.mb.livedataservice.helper;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RestResponsePage extends PageImpl {
+
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+ public RestResponsePage(@JsonProperty("content") List content,
+ @JsonProperty("number") int number,
+ @JsonProperty("size") int size,
+ @JsonProperty("totalElements") Long totalElements,
+ @JsonProperty("pageable") JsonNode pageable,
+ @JsonProperty("first") boolean first,
+ @JsonProperty("last") boolean last,
+ @JsonProperty("totalPages") int totalPages,
+ @JsonProperty("sort") JsonNode sort,
+ @JsonProperty("numberOfElements") int numberOfElements) {
+ super(content, PageRequest.of(number, size), totalElements);
+ }
+
+ public RestResponsePage(List content, Pageable pageable, long total) {
+ super(content, pageable, total);
+ }
+
+ public RestResponsePage(List content) {
+ super(content);
+ }
+
+ public RestResponsePage() {
+ super(new ArrayList<>());
+ }
+}
diff --git a/src/test/java/com/mb/livedataservice/integration_tests/api/controller/TutorialControllerTest.java b/src/test/java/com/mb/livedataservice/integration_tests/api/controller/TutorialControllerTest.java
index 6f378a8..9f1143d 100644
--- a/src/test/java/com/mb/livedataservice/integration_tests/api/controller/TutorialControllerTest.java
+++ b/src/test/java/com/mb/livedataservice/integration_tests/api/controller/TutorialControllerTest.java
@@ -5,6 +5,7 @@
import com.mb.livedataservice.api.response.ApiTutorialResponse;
import com.mb.livedataservice.base.BaseUnitTest;
import com.mb.livedataservice.data.model.Tutorial;
+import com.mb.livedataservice.helper.RestResponsePage;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -12,12 +13,15 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.KafkaContainer;
import org.testcontainers.containers.PostgreSQLContainer;
@@ -149,4 +153,24 @@ void shouldGetAllTutorialsByPublishedTrue() {
assertThat(tutorials.length).isGreaterThan(100);
}
-}
\ No newline at end of file
+
+ @Test
+ void shouldGetAllTutorialsByFilter() {
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ UriComponents uriComponents = UriComponentsBuilder.fromPath("/api/tutorials/filter")
+ .queryParam("pageSize", "2")
+ .queryParam("page", "0")
+ .queryParam("description", "Description1")
+ .queryParam("published", true)
+ .build();
+
+ ResponseEntity> tutorials = restTemplate.exchange(uriComponents.toString(), HttpMethod.GET, null, responseType);
+ RestResponsePage tutorialsBody = tutorials.getBody();
+
+ assertThat(tutorials.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(tutorialsBody).isNotNull();
+ assertThat(tutorialsBody.getNumberOfElements()).isGreaterThanOrEqualTo(1);
+ }
+}
diff --git a/src/test/java/com/mb/livedataservice/mapper/TutorialMapperTest.java b/src/test/java/com/mb/livedataservice/mapper/TutorialMapperTest.java
index cd4c815..bddec07 100644
--- a/src/test/java/com/mb/livedataservice/mapper/TutorialMapperTest.java
+++ b/src/test/java/com/mb/livedataservice/mapper/TutorialMapperTest.java
@@ -1,9 +1,11 @@
package com.mb.livedataservice.mapper;
+import com.mb.livedataservice.api.filter.ApiTutorialFilter;
import com.mb.livedataservice.api.request.ApiTutorialRequest;
import com.mb.livedataservice.api.request.ApiTutorialUpdateRequest;
import com.mb.livedataservice.api.response.ApiTutorialResponse;
import com.mb.livedataservice.base.BaseUnitTest;
+import com.mb.livedataservice.data.filter.TutorialFilter;
import com.mb.livedataservice.data.model.Tutorial;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
@@ -73,4 +75,18 @@ void map_ListOfTutorialToListOfApiTutorialResponse_ShouldSucceed() {
assertEquals(tutorials.getFirst().getDescription(), result.getFirst().getDescription());
assertEquals(tutorials.getFirst().isPublished(), result.getFirst().isPublished());
}
+
+ @Test
+ void map_ApiTutorialFilterToTutorialFilter_ShouldSucceed() {
+ // arrange
+ ApiTutorialFilter apiTutorialFilter = getApiTutorialFilter();
+
+ // act
+ TutorialFilter result = tutorialMapper.map(apiTutorialFilter);
+
+ // assertion
+ assertEquals(apiTutorialFilter.getTitle(), result.getTitle());
+ assertEquals(apiTutorialFilter.getDescription(), result.getDescription());
+ assertEquals(apiTutorialFilter.isPublished(), result.isPublished());
+ }
}