package ru.soune.nocopy.service.violation; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.data.domain.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.soune.nocopy.dto.file.YandexSearchResponse; import ru.soune.nocopy.dto.violation.FileViolationSummaryDTO; import ru.soune.nocopy.dto.violation.ViolationStatisticsResponse; import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.search.GlobalSearchResult; import ru.soune.nocopy.entity.violation.Violation; import ru.soune.nocopy.repository.ComplaintEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.LawCaseRepository; import ru.soune.nocopy.repository.ViolationRepository; import ru.soune.nocopy.service.file.FileEntityService; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @Service @AllArgsConstructor @Slf4j public class ViolationService { private final ViolationRepository violationRepository; private final FileEntityRepository fileEntityRepository; private final FileEntityService fileEntityService; private final ComplaintEntityRepository complaintRepository; private final LawCaseRepository lawCaseRepository; public void processViolation(YandexSearchResponse.ImageResult imageResult, FileEntity file, GlobalSearchResult result) { String url = imageResult.getUrl(); String urlHash = DigestUtils.sha256Hex(url); if (!violationRepository.existsByUrlHash(urlHash)) { Violation violation = new Violation(); if (result != null) { violation.setGlobalSearchResult(result); } violation.setHost(imageResult.getHost()); violation.setUrl(url); violation.setUrlHash(urlHash); violation.setPageUrl(imageResult.getPageUrl()); violation.setPageTitle(imageResult.getPageTitle()); violation.setFileEntity(file); violation.setCreatedDate(LocalDateTime.now()); violation.setStatus(ViolationStatus.NEW.name()); violationRepository.save(violation); } } public Violation getById(Long violationId) { return violationRepository.findById(violationId).orElse(null); } public Page getFileViolationsSummary(Long userId, String fileName, String sortDirection, Integer page, Integer size, LocalDateTime start, LocalDateTime end) { List userFiles = fileName == null || fileName.isEmpty() ? fileEntityService.getAllUserFiles(userId) : fileEntityService.getAllUserFilesWithFileName(userId, fileName, List.of(FileStatus.ACTIVE, FileStatus.BLOCKED, FileStatus.MODERATION)); Sort sort = Sort.by(sortDirection.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC, "createdDate"); Pageable pageable = PageRequest.of(page, size, sort); Page violations = violationRepository.findByFileEntityInAndStatusNotAndCreatedDateBetween( userFiles, ViolationStatus.NOT_OWNER_FILE.name(), start, end, pageable); Map> violationsByFileId = violations.getContent().stream() .collect(Collectors.groupingBy(v -> v.getFileEntity().getId())); List dtoList = violationsByFileId.entrySet().stream() .map(entry -> { List fileViolations = entry.getValue(); FileEntity file = fileViolations.get(0).getFileEntity(); LocalDateTime latestDate = fileViolations.stream() .map(Violation::getCreatedDate) .max(LocalDateTime::compareTo) .orElse(null); return FileViolationSummaryDTO.builder() .fileId(file.getId()) .supportId(file.getSupportId()) .fileName(file.getOriginalFileName()) .fileSize(file.getFileSize()) .mimeType(file.getMimeType()) .status(file.getStatus()) .createdAt(file.getCreatedAt()) .violationCount(fileViolations.size()) .latestViolationDate(latestDate) .build(); }) .collect(Collectors.toList()); return new PageImpl<>(dtoList, pageable, violations.getTotalElements()); } @Transactional public void changeStatus(ViolationStatus status, Long violationId) { Violation violation = violationRepository.findById(violationId).orElseThrow(); violation.setStatus(status.name()); violationRepository.save(violation); } public Page getViolationsByFiles(List files, int page, int size, String sortDirection) { Sort sort = Sort.by(sortDirection.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC, "createdDate"); Pageable pageable = PageRequest.of(page, size, sort); return violationRepository.findByFileEntityInAndStatusNot(files, ViolationStatus.NOT_OWNER_FILE.name(), pageable); } public Page getViolationsByFilesAndStatus(List files, String status, int page, int size, String sortDirection) { Sort sort = Sort.by(sortDirection.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC, "createdDate"); Pageable pageable = PageRequest.of(page, size, sort); return violationRepository.findByFileEntityInAndStatus(files, status, pageable); } public Page getViolationsByFilesAndDateRange(List files, LocalDateTime startDate, LocalDateTime endDate, int page, int size, String sortDirection) { Sort sort = Sort.by(sortDirection.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC, "createdDate"); Pageable pageable = PageRequest.of(page, size, sort); return violationRepository.findByFileEntityInAndStatusNotAndCreatedDateBetween(files, ViolationStatus.NOT_OWNER_FILE.name(), startDate, endDate, pageable); } public Map getGroupedViolations(List files, String groupBy, String status, LocalDateTime startDate, LocalDateTime endDate, String sortDirection) { List violations; if (status != null && !status.isEmpty() && startDate != null && endDate != null) { violations = violationRepository.findByFileEntityInAndStatusAndCreatedDateBetween(files, status, startDate, endDate); } else if (status != null && !status.isEmpty()) { violations = violationRepository.findByFileEntityInAndStatus(files, status); } else if (startDate != null && endDate != null) { violations = violationRepository.findByFileEntityInAndCreatedDateBetweenAndStatusNot(files, startDate, endDate, ViolationStatus.NOT_OWNER_FILE.name()); } else { violations = violationRepository.findByFileEntityInAndStatusNot(files, ViolationStatus.NOT_OWNER_FILE.name()); } Map grouped = violations.stream() .collect(Collectors.groupingBy( v -> extractGroupKey(v.getPageUrl(), groupBy), Collectors.counting())); return grouped.entrySet().stream() .sorted((e1, e2) -> { if (sortDirection.equalsIgnoreCase("desc")) { return e2.getValue().compareTo(e1.getValue()); } else { return e1.getValue().compareTo(e2.getValue()); } }) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); } 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 = fileEntityService.getAllUserFiles(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.findByFileEntityInAndCreatedDateBetweenAndStatusNot(userFiles, startDate, endDate, ViolationStatus.NOT_OWNER_FILE.name()); } else { violations = violationRepository.findByFileEntityInAndStatusNot(userFiles, ViolationStatus.NOT_OWNER_FILE.name()); } Set inProgressStatuses = Set.of( ViolationStatus.LEGAL_IN_WORK.name(), ViolationStatus.COMPLAINT_IN_WORK.name(), ViolationStatus.COMPLAINT_AND_LEGAL_IN_WORK.name()); List violationIds = violations.stream().map(Violation::getId).toList(); long newViolations = violations.stream() .filter(v -> v.getStatus().equals(ViolationStatus.NEW.name())) .count(); long inProgressViolations = violations.stream() .filter(v -> inProgressStatuses.contains(v.getStatus())) .count(); long resolvedViolations = violations.stream() .filter(v -> v.getStatus().equals(ViolationStatus.AUTHORIZED_USE.name())) .count(); Long totalComplaints = 0L; Long totalLawCases = 0L; if (!violationIds.isEmpty()) { totalComplaints = complaintRepository.countComplaintByViolations(violationIds); totalLawCases = lawCaseRepository.countLawCaseByViolationIds(violationIds); } return ViolationStatisticsResponse.builder() // .totalViolations(violations.size()) // .newViolations(newViolations) // .inProgressViolations(inProgressViolations) .resolvedViolations(resolvedViolations) .totalComplaints(totalComplaints) .totalLawCases(totalLawCases) .build(); } private String extractGroupKey(String url, String groupBy) { try { String domain = url.replaceAll("https?://(www\\.)?", "").split("/")[0]; if ("tld".equalsIgnoreCase(groupBy)) { int lastDot = domain.lastIndexOf('.'); return lastDot > 0 ? domain.substring(lastDot + 1) : domain; } return domain; } catch (Exception e) { return "unknown"; } } }