Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ dummy.api.base-url=https://dummyjson.com/
| `POST` | `/random-users` | Create a new user | ✅ |
| `PUT` | `/random-users/{id}` | Update user | ✅ |
| `DELETE` | `/random-users/{id}` | Delete user | ✅ |
| `GET` | `/random-users/filter` | Filter users by criteria | ✅ |

### Example Request

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

# Filter users by gender and nationality
curl -X GET "http://localhost:8080/random-users/filter?gender=MALE&nat=FR"

# Filter users by firstname (partial match, case-insensitive)
curl -X GET "http://localhost:8080/random-users/filter?firstname=john"

# Update user
curl -X PUT "http://localhost:8080/random-users/1" \
-H "Content-Type: application/json" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.xpeho.spring_boot_java_random_user.data.models.database.User;
import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -47,4 +48,21 @@ public UserEntity save(UserEntity user) {
public void deleteById(long id) {
userRepository.deleteById(id);
}

@Override
public List<UserEntity> filterUsers(UserFilter filter) {
String gender = filter.gender() != null ? filter.gender().name().toLowerCase() : null;

return userRepository.findByFilters(
gender,
filter.firstname(),
filter.lastname(),
filter.civility(),
filter.email(),
filter.phone(),
filter.nat()
).stream()
.map(userConverter::toDomain)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
package com.xpeho.spring_boot_java_random_user.data.sources.database;

import com.xpeho.spring_boot_java_random_user.data.models.database.User;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface UserRepository extends CrudRepository<User, Long> {

@Query("SELECT * FROM users WHERE " +
"(:gender IS NULL OR LOWER(gender) = LOWER(:gender)) AND " +
"(:firstname IS NULL OR LOWER(firstname) LIKE LOWER(CONCAT('%', :firstname, '%'))) AND " +
"(:lastname IS NULL OR LOWER(lastname) LIKE LOWER(CONCAT('%', :lastname, '%'))) AND " +
"(:civility IS NULL OR civility LIKE CONCAT('%', :civility, '%')) AND " +
"(:email IS NULL OR email LIKE CONCAT('%', :email, '%')) AND " +
"(:phone IS NULL OR phone LIKE CONCAT('%', :phone, '%')) AND " +
"(:nationality IS NULL OR LOWER(nationality) LIKE LOWER(CONCAT('%', :nationality, '%')))")
List<User> findByFilters(
@Param("gender") String gender,
@Param("firstname") String firstname,
@Param("lastname") String lastname,
@Param("civility") String civility,
@Param("email") String email,
@Param("phone") String phone,
@Param("nationality") String nationality
);
Comment thread
profotoce59 marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.xpeho.spring_boot_java_random_user.domain.entities;

import com.xpeho.spring_boot_java_random_user.domain.enums.Gender;

public record UserFilter(
Gender gender,
String firstname,
String lastname,
String civility,
String email,
String phone,
String nat
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.xpeho.spring_boot_java_random_user.domain.enums;

public enum Gender {
MALE,
FEMALE
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.xpeho.spring_boot_java_random_user.domain.services;

import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;

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

void deleteById(long id);

List<UserEntity> filterUsers(UserFilter filter);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.xpeho.spring_boot_java_random_user.domain.usecases;

import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class FilterUsersUseCase {
private final LocalUserService userService;

public FilterUsersUseCase(LocalUserService userService) {
this.userService = userService;
}

public List<UserEntity> execute(UserFilter filter) {
return userService.filterUsers(filter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest;
import com.xpeho.spring_boot_java_random_user.domain.enums.Gender;
import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource;
import com.xpeho.spring_boot_java_random_user.presentation.dto.UserResponseDTO;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -13,6 +14,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;



@RequestMapping("/random-users")
Expand Down Expand Up @@ -92,6 +95,32 @@ ResponseEntity<UserEntity> updateRandomUser(
ResponseEntity<UserEntity> createUser(@RequestBody UserRequest user);


@GetMapping("/filter")
@Operation(
summary = "Filter users",
description = "Search users by optional filters on gender, firstname, lastname, civility, email, phone and nationality. All filters are case-insensitive and support partial matching.",
parameters = {
@Parameter(name = "gender", description = "Filter by gender (MALE or FEMALE)"),
@Parameter(name = "firstname", description = "Filter by firstname"),
@Parameter(name = "lastname", description = "Filter by lastname"),
@Parameter(name = "civility", description = "Filter by civility"),
@Parameter(name = "email", description = "Filter by email"),
@Parameter(name = "phone", description = "Filter by phone"),
@Parameter(name = "nat", description = "Filter by nationality")
}
)
@ApiResponse(responseCode = "200", description = "Filtered list of users")
@ApiResponse(responseCode = "500", description = "Internal server error")
ResponseEntity<List<UserEntity>> filterUsers(
@RequestParam(required = false) Gender gender,
@RequestParam(required = false) String firstname,
@RequestParam(required = false) String lastname,
@RequestParam(required = false) String civility,
@RequestParam(required = false) String email,
@RequestParam(required = false) String phone,
@RequestParam(required = false) String nat
);

@DeleteMapping("/{id}")
@Operation(
summary = "Delete user by id",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.xpeho.spring_boot_java_random_user.domain.entities.PaginatedUsers;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest;
import com.xpeho.spring_boot_java_random_user.domain.enums.Gender;
import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource;
import com.xpeho.spring_boot_java_random_user.domain.exceptions.InvalidPaginationException;
import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException;
Expand All @@ -17,6 +19,7 @@
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.List;


@RestController
Expand All @@ -30,20 +33,22 @@ public class UserHandler implements UserController {
private final GetUserByIdUseCase getUserByIdUseCase;
private final CreateUserUseCase createUserUseCase;
private final DeleteUserByIdUseCase deleteUserUseCase;
private final FilterUsersUseCase filterUsersUseCase;

public UserHandler(
FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase,
UpdateRandomUserUseCase updateRandomUserUseCase,
GetUserByIdUseCase getUserByIdUseCase,
CreateUserUseCase createUserUseCase,
DeleteUserByIdUseCase deleteUserUseCase
DeleteUserByIdUseCase deleteUserUseCase,
FilterUsersUseCase filterUsersUseCase
) {
this.fetchAndSaveRandomUsersUseCase = fetchAndSaveRandomUsersUseCase;
this.updateRandomUserUseCase = updateRandomUserUseCase;
this.getUserByIdUseCase = getUserByIdUseCase;
this.createUserUseCase = createUserUseCase;
this.deleteUserUseCase = deleteUserUseCase;

this.filterUsersUseCase = filterUsersUseCase;
}


Expand Down Expand Up @@ -99,6 +104,16 @@ public ResponseEntity<UserEntity> createUser(@RequestBody UserRequest user) {
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}

@Override
public ResponseEntity<List<UserEntity>> filterUsers(
Gender gender, String firstname, String lastname,
String civility, String email, String phone, String nat
) {
UserFilter filter = new UserFilter(gender, firstname, lastname, civility, email, phone, nat);
List<UserEntity> users = filterUsersUseCase.execute(filter);
return ResponseEntity.ok(users);
}

@Override
public void deleteUserById(int id) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
import com.xpeho.spring_boot_java_random_user.data.models.database.User;
import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
import com.xpeho.spring_boot_java_random_user.domain.enums.Gender;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -84,4 +88,63 @@ void shouldSaveMappedUserAndReturnMappedDomainEntity() {
verify(userRepository).save(daoToSave);
verify(userConverter).toDomain(savedDao);
}

@Test
@DisplayName("Should convert gender enum to lowercase and call repository with filter values")
void shouldFilterUsersWithGender() {
UserFilter filter = new UserFilter(Gender.MALE, "John", null, null, null, null, null);

User dao = new User();
dao.setId(1L);
dao.setFirstname("John");

UserEntity expected = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR");

when(userRepository.findByFilters("male", "John", null, null, null, null, null))
.thenReturn(List.of(dao));
when(userConverter.toDomain(dao)).thenReturn(expected);

List<UserEntity> result = userService.filterUsers(filter);

assertEquals(1, result.size());
assertEquals(expected, result.get(0));
verify(userRepository).findByFilters("male", "John", null, null, null, null, null);
verify(userConverter).toDomain(dao);
}

@Test
@DisplayName("Should pass null gender when filter gender is null")
void shouldFilterUsersWithNullGender() {
UserFilter filter = new UserFilter(null, null, "Smith", null, null, null, null);

User dao = new User();
dao.setId(2L);
dao.setLastname("Smith");

UserEntity expected = new UserEntity(2L, "female", "Alice", "Smith", "Ms", "alice@smith.com", "5678", "pic2.jpg", "US");

when(userRepository.findByFilters(null, null, "Smith", null, null, null, null))
.thenReturn(List.of(dao));
when(userConverter.toDomain(dao)).thenReturn(expected);

List<UserEntity> result = userService.filterUsers(filter);

assertEquals(1, result.size());
assertEquals(expected, result.get(0));
verify(userRepository).findByFilters(null, null, "Smith", null, null, null, null);
}

@Test
@DisplayName("Should return empty list when no users match filter")
void shouldReturnEmptyListWhenNoUsersMatchFilter() {
UserFilter filter = new UserFilter(Gender.FEMALE, "Unknown", null, null, null, null, null);

when(userRepository.findByFilters("female", "Unknown", null, null, null, null, null))
.thenReturn(Collections.emptyList());

List<UserEntity> result = userService.filterUsers(filter);

assertTrue(result.isEmpty());
verify(userRepository).findByFilters("female", "Unknown", null, null, null, null, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.xpeho.spring_boot_java_random_user.domain.usecases;

import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter;
import com.xpeho.spring_boot_java_random_user.domain.enums.Gender;
import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class FilterUsersUseCaseTest {
private LocalUserService userService;
private FilterUsersUseCase useCase;

@BeforeEach
void setUp() {
userService = mock(LocalUserService.class);
useCase = new FilterUsersUseCase(userService);
}

@Test
@DisplayName("Should return filtered users matching the filter")
void shouldReturnFilteredUsers() {
UserFilter filter = new UserFilter(Gender.MALE, "John", null, null, null, null, null);
List<UserEntity> expected = List.of(
new UserEntity(1L, "male", "John", "Doe", "Mr", "john@example.com", "0600000000", "http://pic.jpg", "FR")
);
when(userService.filterUsers(filter)).thenReturn(expected);

List<UserEntity> result = useCase.execute(filter);

assertEquals(expected, result);
verify(userService).filterUsers(filter);
}

@Test
@DisplayName("Should return empty list when no users match the filter")
void shouldReturnEmptyListWhenNoMatch() {
UserFilter filter = new UserFilter(Gender.FEMALE, "Unknown", null, null, null, null, null);
when(userService.filterUsers(filter)).thenReturn(Collections.emptyList());

List<UserEntity> result = useCase.execute(filter);

assertTrue(result.isEmpty());
verify(userService).filterUsers(filter);
}

@Test
@DisplayName("Should pass filter with all fields to the service")
void shouldPassFilterWithAllFields() {
UserFilter filter = new UserFilter(Gender.FEMALE, "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "US");
List<UserEntity> expected = List.of(
new UserEntity(5L, "female", "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "http://pic2.jpg", "US")
);
when(userService.filterUsers(filter)).thenReturn(expected);

List<UserEntity> result = useCase.execute(filter);

assertEquals(expected, result);
verify(userService, times(1)).filterUsers(filter);
verifyNoMoreInteractions(userService);
}
}
Loading
Loading