diff --git a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java index e159080..223c94d 100644 --- a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java @@ -29,7 +29,8 @@ public class HandlerConfig { PaymentHandler paymentHandler, CostHandler costHandler, MonitoringHandler monitoringHandler, - ViolationHandler violationHandler + ViolationHandler violationHandler, + ViolationStatisticsHandler violationStatisticsHandler ) { Map map = new HashMap<>(); map.put(20001, login); @@ -50,6 +51,7 @@ public class HandlerConfig { map.put(30007, monitoringHandler); map.put(30008, costHandler); map.put(30009, violationHandler); + map.put(30010, violationStatisticsHandler); return map; } diff --git a/src/main/java/ru/soune/nocopy/controller/GlobalSearchController.java b/src/main/java/ru/soune/nocopy/controller/GlobalSearchController.java index c388962..2d8078c 100644 --- a/src/main/java/ru/soune/nocopy/controller/GlobalSearchController.java +++ b/src/main/java/ru/soune/nocopy/controller/GlobalSearchController.java @@ -1,26 +1,20 @@ package ru.soune.nocopy.controller; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.soune.nocopy.dto.file.YandexSearchResponse; -import ru.soune.nocopy.dto.search.GlobalSearchFileResult; import ru.soune.nocopy.dto.search.GlobalSearchStartRequest; import ru.soune.nocopy.dto.search.GlobalSearchStartResponse; import ru.soune.nocopy.dto.search.GlobalSearchStatusResponse; import ru.soune.nocopy.entity.file.FileEntity; -import ru.soune.nocopy.entity.search.GlobalSearchResult; import ru.soune.nocopy.entity.search.GlobalSearchTask; import ru.soune.nocopy.entity.search.SearchStatus; -import ru.soune.nocopy.repository.GlobalSearchResultRepository; import ru.soune.nocopy.repository.GlobalSearchTaskRepository; import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.search.GlobalSearchService; import java.util.*; -import java.util.stream.Collectors; @Slf4j @RestController diff --git a/src/main/java/ru/soune/nocopy/dto/violation/ViolationStatisticsRequest.java b/src/main/java/ru/soune/nocopy/dto/violation/ViolationStatisticsRequest.java new file mode 100644 index 0000000..c9246e7 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/violation/ViolationStatisticsRequest.java @@ -0,0 +1,20 @@ +package ru.soune.nocopy.dto.violation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class ViolationStatisticsRequest { + + @JsonProperty("user_id") + private Long userId; + + @JsonProperty("file_id") + private String fileId; + + @JsonProperty("start_date") + private String startDate; + + @JsonProperty("end_date") + private String endDate; +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/dto/violation/ViolationStatisticsResponse.java b/src/main/java/ru/soune/nocopy/dto/violation/ViolationStatisticsResponse.java new file mode 100644 index 0000000..64d143c --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/violation/ViolationStatisticsResponse.java @@ -0,0 +1,22 @@ +package ru.soune.nocopy.dto.violation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ViolationStatisticsResponse { + + @JsonProperty("total_violations") + private long totalViolations; + + @JsonProperty("new_violations") + private long newViolations; + + @JsonProperty("in_progress_violations") + private long inProgressViolations; + + @JsonProperty("resolved_violations") + private long resolvedViolations; +} diff --git a/src/main/java/ru/soune/nocopy/handler/ViolationStatisticsHandler.java b/src/main/java/ru/soune/nocopy/handler/ViolationStatisticsHandler.java new file mode 100644 index 0000000..a12b6c5 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/handler/ViolationStatisticsHandler.java @@ -0,0 +1,77 @@ +package ru.soune.nocopy.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.soune.nocopy.dto.BaseRequest; +import ru.soune.nocopy.dto.BaseResponse; +import ru.soune.nocopy.dto.violation.ViolationStatisticsRequest; +import ru.soune.nocopy.dto.violation.ViolationStatisticsResponse; +import ru.soune.nocopy.service.violation.ViolationService; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ViolationStatisticsHandler implements RequestHandler { + private final ObjectMapper objectMapper; + + private final ViolationService violationService; + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public BaseResponse handle(BaseRequest request) throws Exception { + ViolationStatisticsRequest statRequest = objectMapper.convertValue( + request.getMessageBody(), ViolationStatisticsRequest.class); + + validateRequest(statRequest); + + LocalDateTime startDate = null; + LocalDateTime endDate = null; + + if (statRequest.getStartDate() != null && statRequest.getEndDate() != null) { + startDate = parseDate(statRequest.getStartDate()); + endDate = parseDate(statRequest.getEndDate()); + + if (endDate != null && !statRequest.getEndDate().contains(" ")) { + endDate = endDate.withHour(23).withMinute(59).withSecond(59); + } + } + + ViolationStatisticsResponse statistics = violationService.getViolationStatistics( + statRequest.getUserId(), + statRequest.getFileId(), + startDate, + endDate + ); + + return BaseResponse.builder() + .msgId(request.getMsgId()) + .messageBody(statistics) + .build(); + } + + private void validateRequest(ViolationStatisticsRequest request) { + if (request.getUserId() == null) { + throw new IllegalArgumentException("User ID is required"); + } + } + + private LocalDateTime parseDate(String dateStr) { + try { + return LocalDateTime.parse(dateStr, DATE_FORMATTER); + } catch (DateTimeParseException e) { + try { + return LocalDateTime.parse(dateStr + " 00:00:00", DATE_FORMATTER); + } catch (DateTimeParseException ex) { + log.error("Error parsing date: {}", dateStr, ex); + return null; + } + } + } +} diff --git a/src/main/java/ru/soune/nocopy/repository/ViolationRepository.java b/src/main/java/ru/soune/nocopy/repository/ViolationRepository.java index ccc6f14..1679f9f 100644 --- a/src/main/java/ru/soune/nocopy/repository/ViolationRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/ViolationRepository.java @@ -36,4 +36,10 @@ public interface ViolationRepository extends JpaRepository { Optional findById(Long id); void deleteByFileEntity(FileEntity file); + + List findByFileEntityIn(List files); + + List findByFileEntityInAndCreatedDateBetween(List files, + LocalDateTime startDate, + LocalDateTime endDate); } \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/service/violation/ViolationService.java b/src/main/java/ru/soune/nocopy/service/violation/ViolationService.java index ab1aa42..d226eb6 100644 --- a/src/main/java/ru/soune/nocopy/service/violation/ViolationService.java +++ b/src/main/java/ru/soune/nocopy/service/violation/ViolationService.java @@ -8,10 +8,12 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import ru.soune.nocopy.dto.file.YandexSearchResponse; +import ru.soune.nocopy.dto.violation.ViolationStatisticsResponse; import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.search.GlobalSearchResult; import ru.soune.nocopy.entity.violation.Violation; import ru.soune.nocopy.exception.ViolationNotFoundException; +import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.ViolationRepository; import java.time.LocalDateTime; @@ -25,6 +27,8 @@ public class ViolationService { private final ViolationRepository violationRepository; + private final FileEntityRepository fileEntityRepository; + public void processViolation(YandexSearchResponse.ImageResult imageResult, FileEntity file, GlobalSearchResult result) { String url = imageResult.getUrl(); @@ -113,4 +117,65 @@ public class ViolationService { return "unknown"; } } + + public ViolationStatisticsResponse getViolationStatistics(Long userId, String fileId, + LocalDateTime startDate, LocalDateTime endDate) { + + List userFiles; + + if (fileId != null && !fileId.isEmpty()) { + FileEntity file = fileEntityRepository.findById(fileId) + .orElseThrow(() -> new IllegalArgumentException("File not found with id: " + fileId)); + + if (!file.getUserId().equals(userId)) { + throw new IllegalArgumentException("File does not belong to user"); + } + + userFiles = List.of(file); + } else { + userFiles = fileEntityRepository.findByUserId(userId); + } + + if (userFiles.isEmpty()) { + return ViolationStatisticsResponse.builder() + .totalViolations(0) + .newViolations(0) + .inProgressViolations(0) + .resolvedViolations(0) + .build(); + } + + List violations; + + if (startDate != null && endDate != null) { + violations = violationRepository.findByFileEntityInAndCreatedDateBetween(userFiles, startDate, endDate); + } else { + violations = violationRepository.findByFileEntityIn(userFiles); + } + + long newViolations = violations.stream() + .filter(v -> "CREATED".equalsIgnoreCase(v.getStatus()) || + "NEW".equalsIgnoreCase(v.getStatus())) + .count(); + + long inProgressViolations = violations.stream() + .filter(v -> "IN_PROGRESS".equalsIgnoreCase(v.getStatus()) || + "PROCESSING".equalsIgnoreCase(v.getStatus()) || + "IN_WORK".equalsIgnoreCase(v.getStatus())) + .count(); + + long resolvedViolations = violations.stream() + .filter(v -> "RESOLVED".equalsIgnoreCase(v.getStatus()) || + "COMPLETED".equalsIgnoreCase(v.getStatus()) || + "SOLVED".equalsIgnoreCase(v.getStatus()) || + "DONE".equalsIgnoreCase(v.getStatus())) + .count(); + + return ViolationStatisticsResponse.builder() + .totalViolations(violations.size()) + .newViolations(newViolations) + .inProgressViolations(inProgressViolations) + .resolvedViolations(resolvedViolations) + .build(); + } } \ No newline at end of file