Skip to content

Commit 4b1e3e1

Browse files
Feat/endpoint usersfilter (#59)
* feat(endpoint-userfilter): add the endpoint * feat(endpoint-userfilter): add enum for gender * feat(endpoint-userfilter): update readme * feat(endpoint-userfilter): add test for usecase and services * feat(endpoint-userfilter): add jpa for api criteria --------- Co-authored-by: Romain Vanhee <romain.vanhee@yrycom.com>
1 parent 99fcfad commit 4b1e3e1

16 files changed

Lines changed: 415 additions & 27 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ dummy.api.base-url=https://dummyjson.com/
8888
| `POST` | `/random-users` | Create a new user ||
8989
| `PUT` | `/random-users/{id}` | Update user ||
9090
| `DELETE` | `/random-users/{id}` | Delete user ||
91+
| `GET` | `/random-users/filter` | Filter users by criteria ||
9192

9293
### Example Request
9394

@@ -115,6 +116,12 @@ curl -X POST "http://localhost:8080/random-users" \
115116
"nat": "FR"
116117
}'
117118

119+
# Filter users by gender and nationality
120+
curl -X GET "http://localhost:8080/random-users/filter?gender=MALE&nat=FR"
121+
122+
# Filter users by firstname (partial match, case-insensitive)
123+
curl -X GET "http://localhost:8080/random-users/filter?firstname=john"
124+
118125
# Update user
119126
curl -X PUT "http://localhost:8080/random-users/1" \
120127
-H "Content-Type: application/json" \

pom.xml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
</dependency>
5151
<dependency>
5252
<groupId>org.springframework.boot</groupId>
53-
<artifactId>spring-boot-starter-data-jdbc</artifactId>
53+
<artifactId>spring-boot-starter-data-jpa</artifactId>
5454
</dependency>
5555
<dependency>
5656
<groupId>org.springframework.boot</groupId>
@@ -78,11 +78,6 @@
7878
<groupId>org.springframework.boot</groupId>
7979
<artifactId>spring-boot-starter-actuator-test</artifactId>
8080
<scope>test</scope>
81-
</dependency>
82-
<dependency>
83-
<groupId>org.springframework.boot</groupId>
84-
<artifactId>spring-boot-starter-data-jdbc-test</artifactId>
85-
<scope>test</scope>
8681
</dependency>
8782
<dependency>
8883
<groupId>org.springframework.boot</groupId>

src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/User.java

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
11
package com.xpeho.spring_boot_java_random_user.data.models.database;
22

3-
import org.springframework.data.annotation.Id;
4-
import org.springframework.data.relational.core.mapping.Column;
5-
import org.springframework.data.relational.core.mapping.Table;
6-
7-
@Table("users")
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.GeneratedValue;
6+
import jakarta.persistence.GenerationType;
7+
import jakarta.persistence.Id;
8+
import jakarta.persistence.Table;
9+
10+
@Entity
11+
@Table(name = "users")
812
public class User {
913
@Id
10-
@Column("id")
14+
@GeneratedValue(strategy = GenerationType.IDENTITY)
15+
@Column(name = "id")
1116
private Long id;
12-
@Column("gender")
17+
@Column(name = "gender")
1318
private String gender;
14-
@Column("firstname")
19+
@Column(name = "firstname")
1520
private String firstname;
16-
@Column("lastname")
21+
@Column(name = "lastname")
1722
private String lastname;
18-
@Column("civility")
23+
@Column(name = "civility")
1924
private String civility;
20-
@Column("email")
25+
@Column(name = "email")
2126
private String email;
22-
@Column("phone")
27+
@Column(name = "phone")
2328
private String phone;
24-
@Column("picture")
29+
@Column(name = "picture")
2530
private String picture;
26-
@Column("nationality")
31+
@Column(name = "nationality")
2732
private String nationality;
2833

29-
// Required by Spring Data JDBC to instantiate the entity via reflection
34+
// Required by JPA
3035
public User() {
31-
// No initialization needed; fields are populated by Spring Data JDBC after instantiation
36+
// No initialization needed
3237
}
3338

3439
public Long getId() {

src/main/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter;
44
import com.xpeho.spring_boot_java_random_user.data.models.database.User;
55
import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository;
6+
import com.xpeho.spring_boot_java_random_user.data.sources.database.UserSpecifications;
67
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
8+
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
79
import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService;
810
import org.springframework.stereotype.Service;
911

@@ -47,4 +49,11 @@ public UserEntity save(UserEntity user) {
4749
public void deleteById(long id) {
4850
userRepository.deleteById(id);
4951
}
52+
53+
@Override
54+
public List<UserEntity> filterUsers(UserFilter filter) {
55+
return userRepository.findAll(UserSpecifications.byFilter(filter)).stream()
56+
.map(userConverter::toDomain)
57+
.toList();
58+
}
5059
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.xpeho.spring_boot_java_random_user.data.sources.database;
22

33
import com.xpeho.spring_boot_java_random_user.data.models.database.User;
4-
import org.springframework.data.repository.CrudRepository;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
56

6-
public interface UserRepository extends CrudRepository<User, Long> {
7+
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
78
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.xpeho.spring_boot_java_random_user.data.sources.database;
2+
3+
import com.xpeho.spring_boot_java_random_user.data.models.database.User;
4+
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
5+
import jakarta.persistence.criteria.CriteriaBuilder;
6+
import jakarta.persistence.criteria.Path;
7+
import jakarta.persistence.criteria.Predicate;
8+
import org.springframework.data.jpa.domain.Specification;
9+
import org.springframework.util.StringUtils;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
14+
public final class UserSpecifications {
15+
private UserSpecifications() {
16+
}
17+
18+
public static Specification<User> byFilter(UserFilter filter) {
19+
return (user, query, criteriaBuilder) -> {
20+
List<Predicate> predicates = new ArrayList<>();
21+
22+
if (filter.gender() != null) {
23+
predicates.add(criteriaBuilder.equal(
24+
criteriaBuilder.lower(user.get("gender")),
25+
filter.gender().name().toLowerCase()
26+
));
27+
}
28+
29+
addContainsPredicate(predicates, criteriaBuilder, user.get("firstname"), filter.firstname());
30+
addContainsPredicate(predicates, criteriaBuilder, user.get("lastname"), filter.lastname());
31+
addContainsPredicate(predicates, criteriaBuilder, user.get("civility"), filter.civility());
32+
addContainsPredicate(predicates, criteriaBuilder, user.get("email"), filter.email());
33+
addContainsPredicate(predicates, criteriaBuilder, user.get("phone"), filter.phone());
34+
addContainsPredicate(predicates, criteriaBuilder, user.get("nationality"), filter.nat());
35+
36+
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
37+
};
38+
}
39+
40+
private static void addContainsPredicate(
41+
List<Predicate> predicates,
42+
CriteriaBuilder criteriaBuilder,
43+
Path<String> field,
44+
String value
45+
) {
46+
if (!StringUtils.hasText(value)) {
47+
return;
48+
}
49+
50+
predicates.add(criteriaBuilder.like(
51+
criteriaBuilder.lower(field),
52+
"%" + value.toLowerCase() + "%"
53+
));
54+
}
55+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.xpeho.spring_boot_java_random_user.domain.entities;
2+
3+
import com.xpeho.spring_boot_java_random_user.domain.enums.Gender;
4+
5+
public record UserFilter(
6+
Gender gender,
7+
String firstname,
8+
String lastname,
9+
String civility,
10+
String email,
11+
String phone,
12+
String nat
13+
) {
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.xpeho.spring_boot_java_random_user.domain.enums;
2+
3+
public enum Gender {
4+
MALE,
5+
FEMALE
6+
}

src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/LocalUserService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.xpeho.spring_boot_java_random_user.domain.services;
22

33
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
4+
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
45

56
import java.util.List;
67
import java.util.Optional;
@@ -13,5 +14,7 @@ public interface LocalUserService {
1314
UserEntity save(UserEntity user);
1415

1516
void deleteById(long id);
17+
18+
List<UserEntity> filterUsers(UserFilter filter);
1619
}
1720

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.xpeho.spring_boot_java_random_user.domain.usecases;
2+
3+
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
4+
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
5+
import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService;
6+
import org.springframework.stereotype.Service;
7+
8+
import java.util.List;
9+
10+
@Service
11+
public class FilterUsersUseCase {
12+
private final LocalUserService userService;
13+
14+
public FilterUsersUseCase(LocalUserService userService) {
15+
this.userService = userService;
16+
}
17+
18+
public List<UserEntity> execute(UserFilter filter) {
19+
return userService.filterUsers(filter);
20+
}
21+
}

0 commit comments

Comments
 (0)