@@ -30,7 +30,8 @@ public class HandlerConfig {
|
|||||||
CostHandler costHandler,
|
CostHandler costHandler,
|
||||||
MonitoringHandler monitoringHandler,
|
MonitoringHandler monitoringHandler,
|
||||||
ViolationHandler violationHandler,
|
ViolationHandler violationHandler,
|
||||||
ViolationStatisticsHandler violationStatisticsHandler
|
ViolationStatisticsHandler violationStatisticsHandler,
|
||||||
|
GlobalSearchHandler globalSearchHandler
|
||||||
) {
|
) {
|
||||||
Map<Integer, RequestHandler> map = new HashMap<>();
|
Map<Integer, RequestHandler> map = new HashMap<>();
|
||||||
map.put(20001, login);
|
map.put(20001, login);
|
||||||
@@ -52,6 +53,7 @@ public class HandlerConfig {
|
|||||||
map.put(30008, costHandler);
|
map.put(30008, costHandler);
|
||||||
map.put(30009, violationHandler);
|
map.put(30009, violationHandler);
|
||||||
map.put(30010, violationStatisticsHandler);
|
map.put(30010, violationStatisticsHandler);
|
||||||
|
map.put(30011, globalSearchHandler);
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.soune.nocopy.dto.search;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class GlobalSearchStatisticsRequest {
|
||||||
|
|
||||||
|
@JsonProperty("token")
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@JsonProperty("limit")
|
||||||
|
private Integer limit = 10;
|
||||||
|
|
||||||
|
@JsonProperty("days")
|
||||||
|
private Integer days;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package ru.soune.nocopy.dto.search;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import ru.soune.nocopy.entity.search.GlobalSearchTask;
|
||||||
|
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class GlobalSearchStatisticsResponse {
|
||||||
|
|
||||||
|
@JsonProperty("total_searches")
|
||||||
|
private long totalSearches;
|
||||||
|
|
||||||
|
@JsonProperty("searches_by_status")
|
||||||
|
private StatusCountDto searchesByStatus;
|
||||||
|
|
||||||
|
@JsonProperty("recent_searches")
|
||||||
|
private List<SearchStatDto> recentSearches;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public static class StatusCountDto {
|
||||||
|
@JsonProperty("processing")
|
||||||
|
private long processing;
|
||||||
|
|
||||||
|
@JsonProperty("completed")
|
||||||
|
private long completed;
|
||||||
|
|
||||||
|
@JsonProperty("failed")
|
||||||
|
private long failed;
|
||||||
|
|
||||||
|
@JsonProperty("total")
|
||||||
|
private long total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public static class SearchStatDto {
|
||||||
|
@JsonProperty("task_id")
|
||||||
|
private String taskId;
|
||||||
|
|
||||||
|
@JsonProperty("search_type")
|
||||||
|
private String searchType;
|
||||||
|
|
||||||
|
@JsonProperty("status")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@JsonProperty("total_files")
|
||||||
|
private Integer totalFiles;
|
||||||
|
|
||||||
|
@JsonProperty("violations_found")
|
||||||
|
private long violationsFound;
|
||||||
|
|
||||||
|
@JsonProperty("created_at")
|
||||||
|
private String createdAt;
|
||||||
|
|
||||||
|
@JsonProperty("duration_seconds")
|
||||||
|
private Long durationSeconds;
|
||||||
|
|
||||||
|
public static SearchStatDto fromEntity(GlobalSearchTask task, long violationsCount) {
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
Long duration = null;
|
||||||
|
if (task.getUpdatedAt() != null && task.getCreatedAt() != null) {
|
||||||
|
duration = java.time.Duration.between(task.getCreatedAt(), task.getUpdatedAt()).getSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchStatDto.builder()
|
||||||
|
.taskId(task.getTaskId())
|
||||||
|
.searchType(task.getSearchType())
|
||||||
|
.status(task.getStatus())
|
||||||
|
.totalFiles(task.getTotalFiles())
|
||||||
|
.violationsFound(violationsCount)
|
||||||
|
.createdAt(task.getCreatedAt() != null ? task.getCreatedAt().format(formatter) : null)
|
||||||
|
.durationSeconds(duration)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,8 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class ViolationStatisticsRequest {
|
public class ViolationStatisticsRequest {
|
||||||
|
|
||||||
@JsonProperty("user_id")
|
@JsonProperty("token")
|
||||||
private Long userId;
|
private String token;
|
||||||
|
|
||||||
@JsonProperty("file_id")
|
@JsonProperty("file_id")
|
||||||
private String fileId;
|
private String fileId;
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
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.search.GlobalSearchStatisticsRequest;
|
||||||
|
import ru.soune.nocopy.dto.search.GlobalSearchStatisticsResponse;
|
||||||
|
import ru.soune.nocopy.entity.user.AuthToken;
|
||||||
|
import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||||
|
import ru.soune.nocopy.service.search.GlobalSearchStatisticsService;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GlobalSearchHandler implements RequestHandler {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
private final GlobalSearchStatisticsService statisticsService;
|
||||||
|
|
||||||
|
private final AuthTokenRepository authTokenRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseResponse handle(BaseRequest request) throws Exception {
|
||||||
|
GlobalSearchStatisticsRequest statRequest = objectMapper.convertValue(
|
||||||
|
request.getMessageBody(), GlobalSearchStatisticsRequest.class);
|
||||||
|
|
||||||
|
if (statRequest.getToken() == null) {
|
||||||
|
throw new IllegalArgumentException("User token is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<AuthToken> tokenOptional =
|
||||||
|
authTokenRepository.findByToken(statRequest.getToken());
|
||||||
|
|
||||||
|
if (tokenOptional.isPresent()) {
|
||||||
|
throw new IllegalArgumentException("User token is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long userId = tokenOptional.orElseThrow().getUser().getId();
|
||||||
|
|
||||||
|
GlobalSearchStatisticsResponse statistics = statisticsService.getStatistics(
|
||||||
|
userId, statRequest.getLimit(), statRequest.getDays());
|
||||||
|
|
||||||
|
return BaseResponse.builder()
|
||||||
|
.msgId(request.getMsgId())
|
||||||
|
.messageBody(statistics)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,11 +8,14 @@ import ru.soune.nocopy.dto.BaseRequest;
|
|||||||
import ru.soune.nocopy.dto.BaseResponse;
|
import ru.soune.nocopy.dto.BaseResponse;
|
||||||
import ru.soune.nocopy.dto.violation.ViolationStatisticsRequest;
|
import ru.soune.nocopy.dto.violation.ViolationStatisticsRequest;
|
||||||
import ru.soune.nocopy.dto.violation.ViolationStatisticsResponse;
|
import ru.soune.nocopy.dto.violation.ViolationStatisticsResponse;
|
||||||
|
import ru.soune.nocopy.entity.user.AuthToken;
|
||||||
|
import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||||
import ru.soune.nocopy.service.violation.ViolationService;
|
import ru.soune.nocopy.service.violation.ViolationService;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -22,6 +25,8 @@ public class ViolationStatisticsHandler implements RequestHandler {
|
|||||||
|
|
||||||
private final ViolationService violationService;
|
private final ViolationService violationService;
|
||||||
|
|
||||||
|
private final AuthTokenRepository authTokenRepository;
|
||||||
|
|
||||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -43,8 +48,17 @@ public class ViolationStatisticsHandler implements RequestHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<AuthToken> tokenOptional =
|
||||||
|
authTokenRepository.findByToken(statRequest.getToken());
|
||||||
|
|
||||||
|
if (tokenOptional.isPresent()) {
|
||||||
|
throw new IllegalArgumentException("User token is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long userId = tokenOptional.orElseThrow().getUser().getId();
|
||||||
|
|
||||||
ViolationStatisticsResponse statistics = violationService.getViolationStatistics(
|
ViolationStatisticsResponse statistics = violationService.getViolationStatistics(
|
||||||
statRequest.getUserId(),
|
userId,
|
||||||
statRequest.getFileId(),
|
statRequest.getFileId(),
|
||||||
startDate,
|
startDate,
|
||||||
endDate
|
endDate
|
||||||
@@ -57,8 +71,8 @@ public class ViolationStatisticsHandler implements RequestHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateRequest(ViolationStatisticsRequest request) {
|
private void validateRequest(ViolationStatisticsRequest request) {
|
||||||
if (request.getUserId() == null) {
|
if (request.getToken() == null) {
|
||||||
throw new IllegalArgumentException("User ID is required");
|
throw new IllegalArgumentException("Token is required");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package ru.soune.nocopy.repository;
|
package ru.soune.nocopy.repository;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import ru.soune.nocopy.entity.search.GlobalSearchResult;
|
import ru.soune.nocopy.entity.search.GlobalSearchResult;
|
||||||
|
|
||||||
@@ -9,5 +11,13 @@ import java.util.List;
|
|||||||
@Repository
|
@Repository
|
||||||
public interface GlobalSearchResultRepository extends JpaRepository<GlobalSearchResult, Long> {
|
public interface GlobalSearchResultRepository extends JpaRepository<GlobalSearchResult, Long> {
|
||||||
List<GlobalSearchResult> findByTaskIdOrderByIdAsc(String taskId);
|
List<GlobalSearchResult> findByTaskIdOrderByIdAsc(String taskId);
|
||||||
|
|
||||||
void deleteByTaskId(String taskId);
|
void deleteByTaskId(String taskId);
|
||||||
|
|
||||||
|
List<GlobalSearchResult> findByTaskId(String taskId);
|
||||||
|
|
||||||
|
@Query("SELECT g.fileId FROM GlobalSearchResult g WHERE g.taskId = :taskId")
|
||||||
|
List<String> findFileIdsByTaskId(@Param("taskId") String taskId);
|
||||||
|
|
||||||
|
long countByTaskId(String taskId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
package ru.soune.nocopy.repository;
|
package ru.soune.nocopy.repository;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import ru.soune.nocopy.entity.search.GlobalSearchTask;
|
import ru.soune.nocopy.entity.search.GlobalSearchTask;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface GlobalSearchTaskRepository extends JpaRepository<GlobalSearchTask, String> {
|
public interface GlobalSearchTaskRepository extends JpaRepository<GlobalSearchTask, String> {
|
||||||
Optional<GlobalSearchTask> findByTaskIdAndUserId(String taskId, Long userId);
|
Optional<GlobalSearchTask> findByTaskIdAndUserId(String taskId, Long userId);
|
||||||
|
|
||||||
|
List<GlobalSearchTask> findByUserIdOrderByCreatedAtDesc(Long userId);
|
||||||
|
|
||||||
|
List<GlobalSearchTask> findByUserIdAndCreatedAtAfterOrderByCreatedAtDesc(Long userId, LocalDateTime date);
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(t) FROM GlobalSearchTask t WHERE t.userId IN :userIds")
|
||||||
|
long countByUserId(@Param("userIds") List<Long> userIds);
|
||||||
|
|
||||||
|
@Query("SELECT t.status, COUNT(t) FROM GlobalSearchTask t WHERE t.userId IN :userIds GROUP BY t.status")
|
||||||
|
List<Object[]> countByStatusAndUserId(@Param("userIds") List<Long> userIds);
|
||||||
|
|
||||||
|
@Query("SELECT t FROM GlobalSearchTask t WHERE t.userId = :userId ORDER BY t.createdAt DESC")
|
||||||
|
List<GlobalSearchTask> findTopByUserIdOrderByCreatedAtDesc(@Param("userId") Long userId,
|
||||||
|
org.springframework.data.domain.Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package ru.soune.nocopy.repository;
|
|||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import ru.soune.nocopy.entity.file.FileEntity;
|
import ru.soune.nocopy.entity.file.FileEntity;
|
||||||
import ru.soune.nocopy.entity.violation.Violation;
|
import ru.soune.nocopy.entity.violation.Violation;
|
||||||
@@ -42,4 +44,10 @@ public interface ViolationRepository extends JpaRepository<Violation, Long> {
|
|||||||
List<Violation> findByFileEntityInAndCreatedDateBetween(List<FileEntity> files,
|
List<Violation> findByFileEntityInAndCreatedDateBetween(List<FileEntity> files,
|
||||||
LocalDateTime startDate,
|
LocalDateTime startDate,
|
||||||
LocalDateTime endDate);
|
LocalDateTime endDate);
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(v) FROM Violation v WHERE v.fileEntity.id IN :fileIds")
|
||||||
|
long countByFileEntityIdIn(@Param("fileIds") List<String> fileIds);
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(v) FROM Violation v WHERE v.globalSearchResult.taskId = :taskId")
|
||||||
|
long countByGlobalSearchResultTaskId(@Param("taskId") String taskId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package ru.soune.nocopy.service.search;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import ru.soune.nocopy.dto.search.GlobalSearchStatisticsResponse;
|
||||||
|
import ru.soune.nocopy.entity.company.Company;
|
||||||
|
import ru.soune.nocopy.entity.search.GlobalSearchTask;
|
||||||
|
import ru.soune.nocopy.entity.user.User;
|
||||||
|
import ru.soune.nocopy.repository.GlobalSearchResultRepository;
|
||||||
|
import ru.soune.nocopy.repository.GlobalSearchTaskRepository;
|
||||||
|
import ru.soune.nocopy.repository.UserRepository;
|
||||||
|
import ru.soune.nocopy.repository.ViolationRepository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GlobalSearchStatisticsService {
|
||||||
|
|
||||||
|
private final GlobalSearchTaskRepository taskRepository;
|
||||||
|
|
||||||
|
private final GlobalSearchResultRepository resultRepository;
|
||||||
|
|
||||||
|
private final ViolationRepository violationRepository;
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public GlobalSearchStatisticsResponse getStatistics(Long userId, Integer limit, Integer days) {
|
||||||
|
User user = userRepository.findById(userId).orElseThrow();
|
||||||
|
Company company = user.getCompany();
|
||||||
|
List<Long> userIds = company == null ? Arrays.asList(userId):
|
||||||
|
user.getCompany()
|
||||||
|
.getUsers()
|
||||||
|
.stream()
|
||||||
|
.map(User::getId).toList();
|
||||||
|
|
||||||
|
long totalSearches = taskRepository.countByUserId(userIds);
|
||||||
|
List<Object[]> statusCounts = taskRepository.countByStatusAndUserId(userIds);
|
||||||
|
|
||||||
|
GlobalSearchStatisticsResponse.StatusCountDto statusCountDto =
|
||||||
|
GlobalSearchStatisticsResponse.StatusCountDto.builder()
|
||||||
|
.processing(0)
|
||||||
|
.completed(0)
|
||||||
|
.failed(0)
|
||||||
|
.total(totalSearches)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (Object[] row : statusCounts) {
|
||||||
|
String status = (String) row[0];
|
||||||
|
Long count = (Long) row[1];
|
||||||
|
|
||||||
|
if ("PROCESSING".equalsIgnoreCase(status)) {
|
||||||
|
statusCountDto.setProcessing(count);
|
||||||
|
} else if ("COMPLETED".equalsIgnoreCase(status) || "SUCCESS".equalsIgnoreCase(status)) {
|
||||||
|
statusCountDto.setCompleted(statusCountDto.getCompleted() + count);
|
||||||
|
} else if ("FAILED".equalsIgnoreCase(status) || "TIMEOUT".equalsIgnoreCase(status)) {
|
||||||
|
statusCountDto.setFailed(statusCountDto.getFailed() + count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GlobalSearchTask> recentTasks;
|
||||||
|
|
||||||
|
if (days != null && days > 0) {
|
||||||
|
LocalDateTime fromDate = LocalDateTime.now().minusDays(days);
|
||||||
|
recentTasks = taskRepository.findByUserIdAndCreatedAtAfterOrderByCreatedAtDesc(userId, fromDate);
|
||||||
|
|
||||||
|
if (limit != null && limit > 0 && recentTasks.size() > limit) {
|
||||||
|
recentTasks = recentTasks.subList(0, limit);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
org.springframework.data.domain.Pageable pageable =
|
||||||
|
org.springframework.data.domain.PageRequest.of(0, limit != null ? limit : 10);
|
||||||
|
recentTasks = taskRepository.findTopByUserIdOrderByCreatedAtDesc(userId, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GlobalSearchStatisticsResponse.SearchStatDto> recentSearchDtos = new ArrayList<>();
|
||||||
|
|
||||||
|
for (GlobalSearchTask task : recentTasks) {
|
||||||
|
long violationsCount = getViolationsCountForTask(task.getTaskId());
|
||||||
|
|
||||||
|
recentSearchDtos.add(GlobalSearchStatisticsResponse.SearchStatDto.fromEntity(task, violationsCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GlobalSearchStatisticsResponse.builder()
|
||||||
|
.totalSearches(totalSearches)
|
||||||
|
.searchesByStatus(statusCountDto)
|
||||||
|
.recentSearches(recentSearchDtos)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public long getViolationsCountForTask(String taskId) {
|
||||||
|
List<String> fileIds = resultRepository.findByTaskId(taskId).stream()
|
||||||
|
.map(result -> result.getFileId())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (fileIds.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return violationRepository.countByFileEntityIdIn(fileIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user