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; import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.user.UserAdditionalInfoBody; import ru.soune.nocopy.dto.user.UserDTO; import ru.soune.nocopy.entity.tarif.TariffInfo; 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.UserService; import ru.soune.nocopy.service.user.UserSpecifications; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @Component @Slf4j @RequiredArgsConstructor public class UserInfoHandler implements RequestHandler { private final AdditionalInfoService additionalInfoService; private final ObjectMapper objectMapper; private final UserRepository userRepository; private final UserService userService; @Override public BaseResponse handle(BaseRequest request) throws Exception { try { UserAdditionalInfoBody body = objectMapper.convertValue(request.getMessageBody(), UserAdditionalInfoBody.class); log.info("Processing UserInfoHandler with action: {}", body.getAction()); return switch (body.getAction()) { case "getAll" -> getAllAdditionalInfos(request); case "getAllWithPagination" -> getAllAdditionalInfoWithPagination(request, body); case "getById" -> getAdditionalInfoById(request, body.getUserId()); case "getIngoById" -> getInfoById(request, body.getUserId()); case "update" -> updateAdditionalInfo(request, body); case "delete" -> deleteAdditionalInfo(request, body.getUserId()); default -> new BaseResponse( request.getMsgId(), MessageCode.INVALID_ACTION.getCode(), "Invalid action: " + body.getAction(), null ); }; } catch (IllegalArgumentException e) { log.error("Validation error in UserInfoHandler", e); return new BaseResponse( request.getMsgId(), MessageCode.VALIDATION_ERROR.getCode(), e.getMessage(), null); } catch (UserAdditionalInfoNotFound e) { log.error("User additional info not found", e); return new BaseResponse( request.getMsgId(), MessageCode.NOT_FOUND.getCode(), e.getMessage(), null); } catch (Exception e) { log.error("Unexpected error in UserInfoHandler", e); return new BaseResponse( request.getMsgId(), MessageCode.INTERNAL_ERROR.getCode(), "Internal server error: " + e.getMessage(), null); } } private BaseResponse getAllAdditionalInfos(BaseRequest request) { log.info("Getting all additional infos"); List infos = additionalInfoService.getAllAdditionalInfos(); List answerItems = infos.stream() .map(this::convertToAnswer) .collect(Collectors.toList()); UserAdditionalInfoListAnswer listAnswer = UserAdditionalInfoListAnswer.builder() .items(answerItems) .count(answerItems.size()) .build(); return new BaseResponse( request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), listAnswer ); } 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"; String sortField = switch (sortBy) { case "fullName" -> "fullName"; case "email" -> "email"; case "createdAt" -> "createdAt"; case "verificationStatus" -> "verificationStatus"; case "tariff" -> "personalTariffInfo.status"; case "tokens" -> "personalTariffInfo.tokens"; default -> "createdAt"; }; Pageable pageable = PageRequest.of(req.getPage(), req.getSize(), Sort.by(direction, sortField)); 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); Page> dtoPage = allUsers.map(user -> { Map map = new LinkedHashMap<>(); map.put("id", user.getId()); map.put("fullName", user.getFullName()); map.put("email", user.getEmail()); map.put("verificationStatus", user.getVerificationStatus() != null ? user.getVerificationStatus().name() : null); map.put("createdAt", user.getCreatedAt() != null ? user.getCreatedAt().toString() : null); TariffInfo tariff = user.getActiveTariffInfo(); if (tariff != null) { map.put("tariff", tariff.getStatus() != null ? tariff.getStatus().name() : null); map.put("tokens", tariff.getTokens() != null ? tariff.getTokens() : 0); } else { map.put("tariff", null); map.put("tokens", 0); } return map; }); Map response = new LinkedHashMap<>(); response.put("content", dtoPage.getContent()); response.put("totalElements", allUsers.getTotalElements()); response.put("totalPages", allUsers.getTotalPages()); response.put("number", allUsers.getNumber()); response.put("size", allUsers.getSize()); return new BaseResponse( request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), response); } private BaseResponse getAdditionalInfoById(BaseRequest request, Long userId) { log.info("Getting additional info for user: {}", userId); if (userId == null) { throw new IllegalArgumentException("User ID is required for getById action"); } UserAdditionalInfo info = additionalInfoService.getAdditionalInfoByUser(userId); if (info == null) { throw new UserAdditionalInfoNotFound( String.format("User additional info not found for userId: %d", userId) ); } UserAdditionalInfoAnswer answer = convertToAnswer(info); return new BaseResponse( request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), answer ); } private BaseResponse getInfoById(BaseRequest request, Long userId) { log.info("Getting info for user: {}", userId); if (userId == null) { throw new IllegalArgumentException("User Id is required for getById action"); } UserDTO userInfo = userService.getUserInfo(userId); return new BaseResponse( request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), userInfo); } private BaseResponse updateAdditionalInfo(BaseRequest request, UserAdditionalInfoBody body) { log.info("Updating additional info for user: {}", body.getUserId()); if (body.getUserId() == null) { throw new IllegalArgumentException("User ID is required for update action"); } validateIpAddress(body.getIpAddress()); validateUserAgent(body.getUserAgent()); Optional updatedInfo = additionalInfoService.updateAdditionalInfo( body.getUserId(), body.getIpAddress(), body.getUserAgent() ); if (updatedInfo.isEmpty()) { throw new UserAdditionalInfoNotFound( String.format("User additional info not found for userId: %d", body.getUserId()) ); } UserAdditionalInfoAnswer answer = convertToAnswer(updatedInfo.get()); return new BaseResponse( request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), answer ); } private BaseResponse deleteAdditionalInfo(BaseRequest request, Long userId) { log.info("Deleting additional info for user: {}", userId); if (userId == null) { throw new IllegalArgumentException("User ID is required for delete action"); } UserAdditionalInfo existingInfo = additionalInfoService.getAdditionalInfoByUser(userId); if (existingInfo == null) { throw new UserAdditionalInfoNotFound( String.format("User additional info not found for userId: %d", userId) ); } additionalInfoService.deleteAdditionalInfo(userId); return new BaseResponse( request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), Map.of("deleted", true, "user_id", userId) ); } private UserAdditionalInfoAnswer convertToAnswer(UserAdditionalInfo info) { return UserAdditionalInfoAnswer.builder() .id(info.getId()) .userId(info.getUser() != null ? info.getUser().getId() : null) .ipAddress(info.getIpAddress()) .userAgent(info.getUserAgent()) .registrationDate(info.getRegistrationDate()) .build(); } private void validateIpAddress(String ip) { if (ip == null || ip.trim().isEmpty()) { throw new IllegalArgumentException("IP address is required for update action"); } String ipv4Pattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; if (!ip.trim().matches(ipv4Pattern)) { log.warn("Invalid IP address format: {}", ip); throw new IllegalArgumentException("Invalid IP address format"); } } private void validateUserAgent(String userAgent) { if (userAgent == null || userAgent.trim().isEmpty()) { throw new IllegalArgumentException("User agent is required for update action"); } if (userAgent.length() > 500) { throw new IllegalArgumentException("User agent must not exceed 500 characters"); } } }