diff --git a/build.gradle b/build.gradle index 19588b9..682f12d 100644 --- a/build.gradle +++ b/build.gradle @@ -73,6 +73,11 @@ dependencies { implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0") implementation("org.springframework.boot:spring-boot-starter-web") + + + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + implementation 'io.github.resilience4j:resilience4j-spring-boot3' + implementation 'org.springframework.boot:spring-boot-starter-aop' } tasks.named('test') { diff --git a/src/main/java/ru/soune/nocopy/dto/user/UserAdditionalInfoBody.java b/src/main/java/ru/soune/nocopy/dto/user/UserAdditionalInfoBody.java index 12fa859..eb2dcd8 100644 --- a/src/main/java/ru/soune/nocopy/dto/user/UserAdditionalInfoBody.java +++ b/src/main/java/ru/soune/nocopy/dto/user/UserAdditionalInfoBody.java @@ -2,6 +2,9 @@ package ru.soune.nocopy.dto.user; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; +import ru.soune.nocopy.entity.user.SubscriptionType; + +import java.time.LocalDate; @Data public class UserAdditionalInfoBody { @@ -17,4 +20,37 @@ public class UserAdditionalInfoBody { @JsonProperty("user_agent") private String userAgent; + + @JsonProperty("page") + private int page = 0; + + @JsonProperty("size") + private int size = 5; + + @JsonProperty("sort_by") + private String sortBy; + + @JsonProperty("sort_direction") + private String sortDirection = "desc"; + + @JsonProperty("full_name") + private String fullName; + + @JsonProperty("email") + private String email; + + @JsonProperty("is_active") + private Boolean isActive; + + @JsonProperty("email_verified") + private Boolean emailVerified; + + @JsonProperty("sub_type") + private SubscriptionType subscriptionType; + + @JsonProperty("date_from") + private LocalDate dateFrom; + + @JsonProperty("date_to") + private LocalDate dateTo; } diff --git a/src/main/java/ru/soune/nocopy/handler/UserInfoHandler.java b/src/main/java/ru/soune/nocopy/handler/UserInfoHandler.java index 07edf10..850723b 100644 --- a/src/main/java/ru/soune/nocopy/handler/UserInfoHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/UserInfoHandler.java @@ -3,6 +3,11 @@ package ru.soune.nocopy.handler; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Component; import ru.soune.nocopy.dto.BaseRequest; import ru.soune.nocopy.dto.BaseResponse; @@ -10,11 +15,14 @@ import ru.soune.nocopy.dto.BaseResponse; import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.user.UserAdditionalInfoBody; +import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.UserAdditionalInfo; import ru.soune.nocopy.entity.user.UserAdditionalInfoAnswer; import ru.soune.nocopy.entity.user.UserAdditionalInfoListAnswer; import ru.soune.nocopy.exception.UserAdditionalInfoNotFound; +import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.service.user.AdditionalInfoService; +import ru.soune.nocopy.service.user.UserSpecifications; import java.util.List; @@ -28,8 +36,11 @@ import java.util.stream.Collectors; public class UserInfoHandler implements RequestHandler { private final AdditionalInfoService additionalInfoService; + private final ObjectMapper objectMapper; + private final UserRepository userRepository; + @Override public BaseResponse handle(BaseRequest request) throws Exception { try { @@ -40,6 +51,7 @@ public class UserInfoHandler implements RequestHandler { return switch (body.getAction()) { case "getAll" -> getAllAdditionalInfos(request); + case "getAllWithPagination" -> getAllAdditionalInfoWithPagination(request, body); case "getById" -> getAdditionalInfoById(request, body.getUserId()); case "update" -> updateAdditionalInfo(request, body); case "delete" -> deleteAdditionalInfo(request, body.getUserId()); @@ -96,6 +108,44 @@ public class UserInfoHandler implements RequestHandler { ); } + private BaseResponse getAllAdditionalInfoWithPagination(BaseRequest request, UserAdditionalInfoBody req) { + Sort.Direction direction = + Sort.Direction.fromString(req.getSortDirection() != null ? req.getSortDirection() : "desc"); + String sortBy = req.getSortBy() != null ? req.getSortBy() : "createdAt"; + + Pageable pageable = PageRequest.of(req.getPage(), req.getSize(), Sort.by(direction, sortBy)); + + Specification spec = (root, query, cb) -> cb.conjunction(); + + if (req.getFullName() != null) { + spec = spec.and(UserSpecifications.byFullName(req.getFullName())); + } + if (req.getEmail() != null) { + spec = spec.and(UserSpecifications.byEmail(req.getEmail())); + } + if (req.getIsActive() != null) { + spec = spec.and(UserSpecifications.byIsActive(req.getIsActive())); + } + if (req.getEmailVerified() != null) { + spec = spec.and(UserSpecifications.byEmailVerified(req.getEmailVerified())); + } + if (req.getSubscriptionType() != null) { + spec = spec.and(UserSpecifications.bySubscriptionType(req.getSubscriptionType())); + } + if (req.getDateFrom() != null || req.getDateTo() != null) { + spec = spec.and(UserSpecifications.byCreatedAtRange(req.getDateFrom(), req.getDateTo())); + } + + Page allUsers = userRepository.findAll(spec, pageable); + + return new BaseResponse( + request.getMsgId(), + MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), + allUsers); + } + + private BaseResponse getAdditionalInfoById(BaseRequest request, Long userId) { log.info("Getting additional info for user: {}", userId); diff --git a/src/main/java/ru/soune/nocopy/repository/UserRepository.java b/src/main/java/ru/soune/nocopy/repository/UserRepository.java index 855e094..5825a87 100644 --- a/src/main/java/ru/soune/nocopy/repository/UserRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/UserRepository.java @@ -1,7 +1,10 @@ package ru.soune.nocopy.repository; import jakarta.validation.constraints.Size; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import ru.soune.nocopy.entity.tarif.TariffInfo; @@ -10,12 +13,17 @@ import ru.soune.nocopy.entity.user.User; import java.util.List; import java.util.Optional; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository, JpaSpecificationExecutor { User findByEmail(String email); + boolean existsByEmail(String email); + boolean existsByPhone(@Size(min = 11, max = 14) String phone); + List findByCompanyId(String companyId); + long countByCompanyId(String companyId); + User findByPersonalTariffInfo(TariffInfo tariffInfo); @Query("SELECT COUNT(u.Id) FROM User u") @@ -48,4 +56,7 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u.email FROM User u WHERE u.company.id = :company") List emailsByCompany(@Param("company") String company); + + @Query("SELECT u FROM User u LEFT JOIN u.personalTariffInfo t") + Page findAllWithTariff(Pageable pageable); } diff --git a/src/main/java/ru/soune/nocopy/service/user/UserSpecifications.java b/src/main/java/ru/soune/nocopy/service/user/UserSpecifications.java new file mode 100644 index 0000000..899114f --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/user/UserSpecifications.java @@ -0,0 +1,59 @@ +package ru.soune.nocopy.service.user; + +import org.springframework.data.jpa.domain.Specification; +import ru.soune.nocopy.entity.user.SubscriptionType; +import ru.soune.nocopy.entity.user.User; + +import java.time.LocalDate; + +public class UserSpecifications { + + public static Specification byFullName(String fullName) { + return (root, query, cb) -> { + if (fullName == null || fullName.isEmpty()) return null; + return cb.like(cb.lower(root.get("fullName")), "%" + fullName.toLowerCase() + "%"); + }; + } + + public static Specification byEmail(String email) { + return (root, query, cb) -> { + if (email == null || email.isEmpty()) return null; + return cb.like(cb.lower(root.get("email")), "%" + email.toLowerCase() + "%"); + }; + } + + public static Specification byIsActive(Boolean isActive) { + return (root, query, cb) -> { + if (isActive == null) return null; + return cb.equal(root.get("isActive"), isActive); + }; + } + + public static Specification byEmailVerified(Boolean emailVerified) { + return (root, query, cb) -> { + if (emailVerified == null) return null; + return cb.equal(root.get("emailVerified"), emailVerified); + }; + } + + public static Specification bySubscriptionType(SubscriptionType type) { + return (root, query, cb) -> { + if (type == null) return null; + return cb.equal(root.get("subscriptionType"), type); + }; + } + + public static Specification byCreatedAtRange(LocalDate from, LocalDate to) { + return (root, query, cb) -> { + if (from == null && to == null) return null; + if (from != null && to != null) { + return cb.between(root.get("createdAt"), + from.atStartOfDay(), to.atTime(23, 59, 59)); + } else if (from != null) { + return cb.greaterThanOrEqualTo(root.get("createdAt"), from.atStartOfDay()); + } else { + return cb.lessThanOrEqualTo(root.get("createdAt"), to.atTime(23, 59, 59)); + } + }; + } +}