From 2c400822839c2b9ddb15e1034f6fa39cc8076413 Mon Sep 17 00:00:00 2001 From: Ivan Shishkin Date: Fri, 22 Sep 2023 21:47:12 +0300 Subject: [PATCH 1/3] 46 - SortRow --- .../fftmback/domain/common/SortRow.java | 45 +++++++++++++++++++ .../com/febfes/fftmback/util/SortUtils.java | 37 +++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/main/java/com/febfes/fftmback/domain/common/SortRow.java create mode 100644 src/main/java/com/febfes/fftmback/util/SortUtils.java diff --git a/src/main/java/com/febfes/fftmback/domain/common/SortRow.java b/src/main/java/com/febfes/fftmback/domain/common/SortRow.java new file mode 100644 index 0000000..0acb562 --- /dev/null +++ b/src/main/java/com/febfes/fftmback/domain/common/SortRow.java @@ -0,0 +1,45 @@ +//package com.febfes.fftmback.domain.common; +// +//import jakarta.validation.constraints.NotBlank; +//import lombok.AllArgsConstructor; +//import lombok.Data; +//import lombok.Getter; +//import org.springframework.data.domain.Sort; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.regex.Matcher; +//import java.util.regex.Pattern; +// +//@Getter +//public class SortRow { +// +// @NotBlank(message = "Property can't be blank when sorting") +// private String property; +// +// private Sort.Direction direction; +// +// public SortRow(String property, String direction) { +// this.property = property; +// this.direction = direction.equals("+") ? Sort.Direction.ASC : Sort.Direction.DESC; +// } +// +// public static SortRow getSortRowFromParam(String sortParam) { +// Pattern pattern = Pattern.compile("([\\-+]?)(\\w+)"); +// Matcher matcher = pattern.matcher(sortParam); +// if (!matcher.find()) { +// throw new IllegalArgumentException("Sort parameter doesn't match pattern"); +// } +// String direction = matcher.group(1); +// String property = matcher.group(2); +// return new SortRow(property, direction); +// } +// +// public static List getSortRowsFromParam(String[] sortParams) { +// List sortRows = new ArrayList<>(); +// for (String sortParam : sortParams) { +// sortRows.add(getSortRowFromParam(sortParam)); +// } +// return sortRows; +// } +//} diff --git a/src/main/java/com/febfes/fftmback/util/SortUtils.java b/src/main/java/com/febfes/fftmback/util/SortUtils.java new file mode 100644 index 0000000..4025e7e --- /dev/null +++ b/src/main/java/com/febfes/fftmback/util/SortUtils.java @@ -0,0 +1,37 @@ +package com.febfes.fftmback.util; + +import lombok.experimental.UtilityClass; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Order; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@UtilityClass +public class SortUtils { + + public static Order getOrderFromParam(String sortParam) { + Pattern pattern = Pattern.compile("([\\-+]?)(\\w+)"); + Matcher matcher = pattern.matcher(sortParam); + if (!matcher.find()) { + throw new IllegalArgumentException("Sort parameter doesn't match pattern"); + } + String direction = matcher.group(1); + String property = matcher.group(2); + return new Order(getDirection(direction), property); + } + + public static List getOrderFromParams(String[] sortParams) { + List sortRows = new ArrayList<>(); + for (String sortParam : sortParams) { + sortRows.add(getOrderFromParam(sortParam)); + } + return sortRows; + } + + private static Sort.Direction getDirection(String direction) { + return direction.equals("+") ? Sort.Direction.ASC : Sort.Direction.DESC; + } +} From e58e19e46db6ae858d504a8458f15338bf84c725 Mon Sep 17 00:00:00 2001 From: Ivan Shishkin Date: Sat, 23 Sep 2023 11:27:36 +0300 Subject: [PATCH 2/3] 46 - Created sort params for projects --- .../febfes/fftmback/annotation/SortParam.java | 17 +++++++ .../controller/ProjectController.java | 9 +++- .../fftmback/domain/common/SortRow.java | 45 ------------------ .../repository/ProjectRepository.java | 4 +- .../fftmback/service/ProjectService.java | 3 +- .../service/impl/ProjectServiceImpl.java | 11 ++++- .../service/impl/TaskServiceImpl.java | 6 +-- .../com/febfes/fftmback/util/CaseUtils.java | 13 +++++ .../integration/ProjectControllerTest.java | 47 ++++++++++++++++--- 9 files changed, 92 insertions(+), 63 deletions(-) create mode 100644 src/main/java/com/febfes/fftmback/annotation/SortParam.java delete mode 100644 src/main/java/com/febfes/fftmback/domain/common/SortRow.java create mode 100644 src/main/java/com/febfes/fftmback/util/CaseUtils.java diff --git a/src/main/java/com/febfes/fftmback/annotation/SortParam.java b/src/main/java/com/febfes/fftmback/annotation/SortParam.java new file mode 100644 index 0000000..09769d5 --- /dev/null +++ b/src/main/java/com/febfes/fftmback/annotation/SortParam.java @@ -0,0 +1,17 @@ +package com.febfes.fftmback.annotation; + +import io.swagger.v3.oas.annotations.Parameter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +@Parameter( + name = "sort", + description = " Sort parameter. ASCENDING = \"+\"; DESCENDING = \"-\". For example: sort=-id&sort=+name" +) +public @interface SortParam { +} diff --git a/src/main/java/com/febfes/fftmback/controller/ProjectController.java b/src/main/java/com/febfes/fftmback/controller/ProjectController.java index 2a35a32..c177d7d 100644 --- a/src/main/java/com/febfes/fftmback/controller/ProjectController.java +++ b/src/main/java/com/febfes/fftmback/controller/ProjectController.java @@ -27,6 +27,8 @@ import java.util.List; +import static com.febfes.fftmback.util.SortUtils.getOrderFromParams; + @RestController @RequestMapping("v1/projects") @RequiredArgsConstructor @@ -41,8 +43,11 @@ public class ProjectController { @Operation(summary = "Get all projects for authenticated user") @ApiGet - public List getProjectsForUser(@AuthenticationPrincipal UserEntity user) { - return projectService.getProjectsForUser(user.getId()); + public List getProjectsForUser( + @SortParam @RequestParam(defaultValue = "-createDate") String[] sort, + @AuthenticationPrincipal UserEntity user + ) { + return projectService.getProjectsForUser(user.getId(), getOrderFromParams(sort)); } @Operation(summary = "Create new project") diff --git a/src/main/java/com/febfes/fftmback/domain/common/SortRow.java b/src/main/java/com/febfes/fftmback/domain/common/SortRow.java deleted file mode 100644 index 0acb562..0000000 --- a/src/main/java/com/febfes/fftmback/domain/common/SortRow.java +++ /dev/null @@ -1,45 +0,0 @@ -//package com.febfes.fftmback.domain.common; -// -//import jakarta.validation.constraints.NotBlank; -//import lombok.AllArgsConstructor; -//import lombok.Data; -//import lombok.Getter; -//import org.springframework.data.domain.Sort; -// -//import java.util.ArrayList; -//import java.util.List; -//import java.util.regex.Matcher; -//import java.util.regex.Pattern; -// -//@Getter -//public class SortRow { -// -// @NotBlank(message = "Property can't be blank when sorting") -// private String property; -// -// private Sort.Direction direction; -// -// public SortRow(String property, String direction) { -// this.property = property; -// this.direction = direction.equals("+") ? Sort.Direction.ASC : Sort.Direction.DESC; -// } -// -// public static SortRow getSortRowFromParam(String sortParam) { -// Pattern pattern = Pattern.compile("([\\-+]?)(\\w+)"); -// Matcher matcher = pattern.matcher(sortParam); -// if (!matcher.find()) { -// throw new IllegalArgumentException("Sort parameter doesn't match pattern"); -// } -// String direction = matcher.group(1); -// String property = matcher.group(2); -// return new SortRow(property, direction); -// } -// -// public static List getSortRowsFromParam(String[] sortParams) { -// List sortRows = new ArrayList<>(); -// for (String sortParam : sortParams) { -// sortRows.add(getSortRowFromParam(sortParam)); -// } -// return sortRows; -// } -//} diff --git a/src/main/java/com/febfes/fftmback/repository/ProjectRepository.java b/src/main/java/com/febfes/fftmback/repository/ProjectRepository.java index 87f0903..65b610d 100644 --- a/src/main/java/com/febfes/fftmback/repository/ProjectRepository.java +++ b/src/main/java/com/febfes/fftmback/repository/ProjectRepository.java @@ -3,6 +3,7 @@ import com.febfes.fftmback.domain.dao.ProjectEntity; import com.febfes.fftmback.domain.projection.ProjectProjection; import com.febfes.fftmback.domain.projection.ProjectWithMembersProjection; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -61,7 +62,8 @@ public interface ProjectRepository extends JpaRepository { FROM project P LEFT JOIN favourite_project FP ON P.id = FP.project_id AND FP.user_id = :userId INNER JOIN user_project UP ON UP.project_id = P.id AND UP.user_id = :userId + ORDER BY "isFavourite" desc, ?#{#sort} """ ) - List getUserProjects(Long userId); + List getUserProjects(Long userId, Sort sort); } diff --git a/src/main/java/com/febfes/fftmback/service/ProjectService.java b/src/main/java/com/febfes/fftmback/service/ProjectService.java index fc2a291..81ba6cc 100644 --- a/src/main/java/com/febfes/fftmback/service/ProjectService.java +++ b/src/main/java/com/febfes/fftmback/service/ProjectService.java @@ -3,6 +3,7 @@ import com.febfes.fftmback.domain.common.specification.TaskSpec; import com.febfes.fftmback.domain.dao.ProjectEntity; import com.febfes.fftmback.dto.*; +import org.springframework.data.domain.Sort; import java.util.List; @@ -10,7 +11,7 @@ public interface ProjectService { ProjectEntity createProject(ProjectEntity project, Long userId); - List getProjectsForUser(Long userId); + List getProjectsForUser(Long userId, List sort); ProjectEntity getProject(Long id); diff --git a/src/main/java/com/febfes/fftmback/service/impl/ProjectServiceImpl.java b/src/main/java/com/febfes/fftmback/service/impl/ProjectServiceImpl.java index 53a15c9..563ee25 100644 --- a/src/main/java/com/febfes/fftmback/service/impl/ProjectServiceImpl.java +++ b/src/main/java/com/febfes/fftmback/service/impl/ProjectServiceImpl.java @@ -22,12 +22,15 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Sort; import org.springframework.data.util.ReflectionUtils; import org.springframework.stereotype.Service; import java.lang.reflect.Field; import java.util.List; +import static com.febfes.fftmback.util.CaseUtils.camelToSnake; + @Slf4j @Service @Transactional @@ -66,8 +69,12 @@ public ProjectEntity createProject( } @Override - public List getProjectsForUser(Long userId) { - List userProjects = projectRepository.getUserProjects(userId); + public List getProjectsForUser(Long userId, List sort) { + List snakeCaseSort = sort.stream() + .map(s -> new Sort.Order(s.getDirection(), camelToSnake(s.getProperty()))) + .toList(); + List userProjects = projectRepository.getUserProjects( + userId, Sort.by(snakeCaseSort)); log.info("Received {} projects for user with id={}", userProjects.size(), userId); return userProjects.stream() .map(ProjectMapper.INSTANCE::projectProjectionToProjectDto) diff --git a/src/main/java/com/febfes/fftmback/service/impl/TaskServiceImpl.java b/src/main/java/com/febfes/fftmback/service/impl/TaskServiceImpl.java index 7c633f2..a4b9ae4 100644 --- a/src/main/java/com/febfes/fftmback/service/impl/TaskServiceImpl.java +++ b/src/main/java/com/febfes/fftmback/service/impl/TaskServiceImpl.java @@ -34,8 +34,6 @@ public class TaskServiceImpl implements TaskService { private static final String RECEIVED_TASKS_SIZE_LOG = "Received tasks size: {}"; - private static final int DEFAULT_PAGE = 0; - private static final int DEFAULT_LIMIT = 1000; private final TaskRepository taskRepository; private final TaskViewRepository taskViewRepository; @@ -62,9 +60,7 @@ public List getTasks( Long columnId, TaskSpec taskSpec ) { - Pageable pageableRequest = PageRequest.of(DEFAULT_PAGE, DEFAULT_LIMIT, Sort.by(ORDER_FIELD_NAME)); - List tasks = taskViewRepository.findAll(taskSpec.and(byColumnId(columnId)), pageableRequest) - .getContent(); + List tasks = taskViewRepository.findAll(taskSpec.and(byColumnId(columnId)), Sort.by(ORDER_FIELD_NAME)); log.info(RECEIVED_TASKS_SIZE_LOG, tasks.size()); return tasks; } diff --git a/src/main/java/com/febfes/fftmback/util/CaseUtils.java b/src/main/java/com/febfes/fftmback/util/CaseUtils.java new file mode 100644 index 0000000..1a05e07 --- /dev/null +++ b/src/main/java/com/febfes/fftmback/util/CaseUtils.java @@ -0,0 +1,13 @@ +package com.febfes.fftmback.util; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class CaseUtils { + + public static String camelToSnake(final String camelStr) { + String ret = camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2") + .replaceAll("([a-z])([A-Z])", "$1_$2"); + return ret.toLowerCase(); + } +} diff --git a/src/test/java/com/febfes/fftmback/integration/ProjectControllerTest.java b/src/test/java/com/febfes/fftmback/integration/ProjectControllerTest.java index 5008aa3..714bc47 100644 --- a/src/test/java/com/febfes/fftmback/integration/ProjectControllerTest.java +++ b/src/test/java/com/febfes/fftmback/integration/ProjectControllerTest.java @@ -25,6 +25,7 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -196,7 +197,7 @@ void successfulSetProjectFavouriteTest() { OneProjectDto updatedProject = projectService.getProjectForUser(createdProjectId, createdUserId); Assertions.assertThat(updatedProject.isFavourite()) .isTrue(); - List userProjects = projectService.getProjectsForUser(createdUserId); + List userProjects = projectService.getProjectsForUser(createdUserId, new ArrayList<>()); Optional userProject = userProjects.stream().findFirst(); Assertions.assertThat(userProject) .isNotEmpty(); @@ -219,7 +220,7 @@ void failedSetProjectFavouriteTest() { OneProjectDto updatedProject = projectService.getProjectForUser(createdProjectId, createdUserId); Assertions.assertThat(updatedProject.isFavourite()) .isFalse(); - List userProjects = projectService.getProjectsForUser(createdUserId); + List userProjects = projectService.getProjectsForUser(createdUserId, new ArrayList<>()); Optional userProject = userProjects.stream().findFirst(); Assertions.assertThat(userProject) .isNotEmpty(); @@ -267,9 +268,11 @@ protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) { List members = userService.getProjectMembersWithRole(createdProjectId); // as owner is also a member Assertions.assertThat(members).hasSize(3); - List secondUserProjects = projectService.getProjectsForUser(secondCreatedUserId); + List secondUserProjects = + projectService.getProjectsForUser(secondCreatedUserId, new ArrayList<>()); Assertions.assertThat(secondUserProjects).hasSize(1); - List thirdUserProjects = projectService.getProjectsForUser(thirdCreatedUserId); + List thirdUserProjects = + projectService.getProjectsForUser(thirdCreatedUserId, new ArrayList<>()); Assertions.assertThat(thirdUserProjects).hasSize(1); } }); @@ -279,7 +282,7 @@ protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) { void successfulRemoveMemberTest() { successfulAddNewMembersTest(); Long secondCreatedUserId = userService.getUserIdByUsername(USER_USERNAME + "1"); - Optional userProject = projectService.getProjectsForUser(createdUserId).stream().findFirst(); + Optional userProject = projectService.getProjectsForUser(createdUserId, new ArrayList<>()).stream().findFirst(); Assertions.assertThat(userProject) .isNotEmpty(); Long createdProjectId = userProject.get().id(); @@ -292,14 +295,14 @@ void successfulRemoveMemberTest() { List members = userService.getProjectMembersWithRole(createdProjectId); Assertions.assertThat(members).hasSize(2); - List secondMemberProjects = projectService.getProjectsForUser(secondCreatedUserId); + List secondMemberProjects = projectService.getProjectsForUser(secondCreatedUserId, new ArrayList<>()); Assertions.assertThat(secondMemberProjects).isEmpty(); } @Test void successfulGetMembersTest() { successfulAddNewMembersTest(); - Long createdProjectId = projectService.getProjectsForUser(createdUserId).get(0).id(); + Long createdProjectId = projectService.getProjectsForUser(createdUserId, new ArrayList<>()).get(0).id(); List projectMembers = requestWithBearerToken() .contentType(ContentType.JSON) .when() @@ -359,6 +362,36 @@ void successfulGetOneProjectTest() { .isEqualTo(RoleName.OWNER); } + @Test + void successfulGetProjectsWithSort() { + projectService.createProject( + ProjectEntity.builder().name(PROJECT_NAME + "1").build(), + createdUserId + ); + projectService.createProject( + ProjectEntity.builder().name(PROJECT_NAME + "2").build(), + createdUserId + ); + + List projects = requestWithBearerToken() + .contentType(ContentType.JSON) + .when() + .get(PATH_TO_PROJECTS_API + "?sort=-id&sort=+name") + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .response() + .as(new TypeRef<>() { + }); + + Assertions.assertThat(projects) + .hasSize(2); + Assertions.assertThat(projects.get(0).name()) + .isEqualTo(PROJECT_NAME + "2"); + Assertions.assertThat(projects.get(1).name()) + .isEqualTo(PROJECT_NAME + "1"); + } + private Long createNewProject(String projectName) { ProjectDto createProjectDto = DtoBuilders.createProjectDto(projectName); Response createResponse = createNewProject(createProjectDto); From 89d753d69d0e594b097431271cfcdb97129a2539 Mon Sep 17 00:00:00 2001 From: Ivan Shishkin Date: Sat, 23 Sep 2023 11:47:46 +0300 Subject: [PATCH 3/3] 46 - fixed camelToSnake case --- src/main/java/com/febfes/fftmback/util/CaseUtils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/febfes/fftmback/util/CaseUtils.java b/src/main/java/com/febfes/fftmback/util/CaseUtils.java index 1a05e07..65e4185 100644 --- a/src/main/java/com/febfes/fftmback/util/CaseUtils.java +++ b/src/main/java/com/febfes/fftmback/util/CaseUtils.java @@ -6,8 +6,7 @@ public class CaseUtils { public static String camelToSnake(final String camelStr) { - String ret = camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2") - .replaceAll("([a-z])([A-Z])", "$1_$2"); + String ret = camelStr.replaceAll("([a-z])([A-Z]+)", "$1_$2"); return ret.toLowerCase(); } }