dev add monitoring basic
Test Workflow / test (push) Successful in 4s

This commit is contained in:
vladp
2026-02-27 17:57:55 +07:00
parent 76aa04e174
commit c09a3a3a18
21 changed files with 815 additions and 7 deletions
@@ -26,7 +26,9 @@ public class HandlerConfig {
ReferralHandler referralHandler, ReferralHandler referralHandler,
ResetPasswordHandler resetPasswordHandler, ResetPasswordHandler resetPasswordHandler,
DaDataHandler daDataHandler, DaDataHandler daDataHandler,
PaymentHandler paymentHandler PaymentHandler paymentHandler,
GetViolationsHandler getViolationsHandler,
MonitoringHandler monitoringHandler
) { ) {
Map<Integer, RequestHandler> map = new HashMap<>(); Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login); map.put(20001, login);
@@ -44,6 +46,8 @@ public class HandlerConfig {
map.put(30003, referralHandler); map.put(30003, referralHandler);
map.put(30004, daDataHandler); map.put(30004, daDataHandler);
map.put(30005, paymentHandler); map.put(30005, paymentHandler);
map.put(30006, getViolationsHandler);
map.put(30007, monitoringHandler);
return map; return map;
} }
@@ -12,11 +12,6 @@ public class YandexSearchResponse {
@JsonProperty("images") @JsonProperty("images")
private List<ImageResult> images; private List<ImageResult> images;
// @JsonProperty("images")
// public void setImages(List<ImageResult> images) {
// this.images = images;
// }
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public static class ImageResult { public static class ImageResult {
@@ -0,0 +1,23 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class GetViolationsRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("page")
private Integer page;
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("mark_as_read")
private boolean markAsRead;
@JsonProperty("auth_token")
private String authToken;
}
@@ -0,0 +1,40 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
import java.time.LocalDateTime;
/**
* Response for status monitoring
*/
@Data
@Builder
public class MonitoringStatusResponse {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("file_name")
private String fileName;
@JsonProperty("monitoring_type")
private MonitoringType monitoringType;
@JsonProperty("next_run")
private LocalDateTime nextRun;
@JsonProperty("last_run")
private LocalDateTime lastRun;
@JsonProperty("last_run_status")
private String lastRunStatus;
@JsonProperty("is_active")
private boolean isActive;
@JsonProperty("new_violations_count")
private long newViolationsCount;
}
@@ -0,0 +1,20 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
/**
Set monitoring request
*/
@Data
public class SetMonitoringRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("monitoring_type")
private MonitoringType monitoringType;
@JsonProperty("auth_token")
private String authToken;
}
@@ -0,0 +1,43 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.monitoring.SearchEngine;
import java.time.LocalDateTime;
@Data
@Builder
public class ViolationResponse {
@JsonProperty("id")
private String id;
@JsonProperty("file_id")
private String fileId;
@JsonProperty("file_name")
private String fileName;
@JsonProperty("found_url")
private String foundUrl;
@JsonProperty("page_url")
private String pageUrl;
@JsonProperty("page_title")
private String pageTitle;
@JsonProperty("host")
private String host;
@JsonProperty("search_engine")
private SearchEngine searchEngine;
@JsonProperty("found_at")
private LocalDateTime foundAt;
@JsonProperty("is_new")
private boolean isNew;
}
@@ -0,0 +1,60 @@
package ru.soune.nocopy.entity.monitoring;
import jakarta.persistence.*;
import lombok.*;
import ru.soune.nocopy.entity.file.FileEntity;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "file_monitoring")
public class FileMonitoringEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "file_id", nullable = false)
private FileEntity file;
@Column(name = "user_id", nullable = false)
private Long userId;
@Enumerated(EnumType.STRING)
@Column(name = "monitoring_type", nullable = false)
private MonitoringType monitoringType;
@Column(name = "next_run", nullable = false)
private LocalDateTime nextRun;
@Column(name = "last_run")
private LocalDateTime lastRun;
@Column(name = "last_run_status")
private String lastRunStatus;
@Column(name = "is_active", nullable = false)
private boolean isActive = true;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,76 @@
package ru.soune.nocopy.entity.monitoring;
import jakarta.persistence.*;
import lombok.*;
import ru.soune.nocopy.entity.file.FileEntity;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "found_violations",
indexes = {
@Index(name = "idx_file_id", columnList = "file_id"),
@Index(name = "idx_found_at", columnList = "found_at")
})
public class FoundViolationEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "file_id", nullable = false)
private FileEntity file;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "file_id_copy", nullable = false)
private String fileId;
@Column(name = "file_name_copy")
private String fileName;
@Column(name = "found_url", nullable = false, length = 2048)
private String foundUrl;
@Column(name = "page_url", length = 2048)
private String pageUrl;
@Column(name = "page_title")
private String pageTitle;
@Column(name = "host")
private String host;
@Column(name = "image_width")
private Integer imageWidth;
@Column(name = "image_height")
private Integer imageHeight;
@Enumerated(EnumType.STRING)
@Column(name = "search_engine", nullable = false)
private SearchEngine searchEngine;
@Column(name = "search_session_id")
private String searchSessionId;
@Column(name = "found_at", nullable = false)
private LocalDateTime foundAt;
@Column(name = "is_new", nullable = false)
private boolean isNew = true;
@Column(name = "is_notified", nullable = false)
private boolean isNotified = false;
@PrePersist
public void prePersist() {
this.foundAt = LocalDateTime.now();
}
}
@@ -0,0 +1,6 @@
package ru.soune.nocopy.entity.monitoring;
public enum MonitoringType {
MONITORING_DAILY, MONITORING_WEEKLY, MONITORING_MONTHLY, NONE
}
@@ -0,0 +1,8 @@
package ru.soune.nocopy.entity.monitoring;
public enum SearchEngine {
YANDEX,
GOOGLE,
BOTH
}
@@ -2,5 +2,6 @@ package ru.soune.nocopy.entity.tarif;
public enum TariffType { public enum TariffType {
START, BASIC, PRO, ENTERPRISE, DEMO, BUISNES, CORPORAT, STUDIO, FREE, TOKEN_100, TOKEN_500, TOKEN_1000, TOKEN_5000; START, BASIC, PRO, ENTERPRISE, DEMO, BUISNES, CORPORAT, STUDIO, FREE, TOKEN_100, TOKEN_500, TOKEN_1000, TOKEN_5000,
MONITORING_DAILY, MONITORING_WEEKLY, MONITORING_MONTHLY;
} }
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class UserNotHavePermission extends RuntimeException {
public UserNotHavePermission(String message) {
super(message);
}
}
@@ -0,0 +1,85 @@
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.MessageCode;
import ru.soune.nocopy.dto.monitoring.GetViolationsRequest;
import ru.soune.nocopy.dto.monitoring.ViolationResponse;
import ru.soune.nocopy.entity.monitoring.FoundViolationEntity;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.FoundViolationRepository;
import ru.soune.nocopy.service.register.AuthService;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Component
@RequiredArgsConstructor
public class GetViolationsHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final FoundViolationRepository violationRepository;
private final AuthService authService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
GetViolationsRequest violationsRequest = objectMapper.convertValue(
request.getMessageBody(), GetViolationsRequest.class);
String authToken = violationsRequest.getAuthToken();
//TODO add return for null token
if (authToken == null) {
return new BaseResponse(0, 0, "", new Object());
}
User user = authService.getAuthToken(authToken).getUser();
List<FoundViolationEntity> violations;
if (violationsRequest.getFileId() != null) {
violations = violationRepository.findByFileIdOrderByFoundAtDesc(
violationsRequest.getFileId());
} else {
violations = violationRepository.findByUserIdOrderByFoundAtDesc(user.getId());
}
if (violationsRequest.isMarkAsRead()) {
violations.stream()
.filter(FoundViolationEntity::isNew)
.forEach(v -> v.setNew(false));
violationRepository.saveAll(violations);
}
List<ViolationResponse> response = violations.stream()
.map(this::mapToResponse)
.collect(Collectors.toList());
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Violations retrieved",
response
);
}
private ViolationResponse mapToResponse(FoundViolationEntity entity) {
return ViolationResponse.builder()
.id(entity.getId())
.fileId(entity.getFileId())
.fileName(entity.getFileName())
.foundUrl(entity.getFoundUrl())
.pageUrl(entity.getPageUrl())
.pageTitle(entity.getPageTitle())
.host(entity.getHost())
.searchEngine(entity.getSearchEngine())
.foundAt(entity.getFoundAt())
.isNew(entity.isNew())
.build();
}
}
@@ -0,0 +1,72 @@
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.MessageCode;
import ru.soune.nocopy.dto.monitoring.MonitoringStatusResponse;
import ru.soune.nocopy.dto.monitoring.SetMonitoringRequest;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
import ru.soune.nocopy.entity.tarif.TariffType;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.TariffRepository;
import ru.soune.nocopy.service.monitoring.FileMonitoringService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tariff.TariffService;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor
public class MonitoringHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final FileMonitoringService monitoringService;
private final AuthService authService;
private final TariffService tariffService;
private final TariffRepository tariffRepository;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
SetMonitoringRequest setRequest = objectMapper.convertValue(
request.getMessageBody(), SetMonitoringRequest.class);
String authToken = setRequest.getAuthToken();
AuthToken userAuthToken = authService.getAuthToken(authToken);
if (authToken == null || userAuthToken == null) {
return new BaseResponse(30007, MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("token", authToken));
}
User user = userAuthToken.getUser();
Long userId = user.getId();
MonitoringType monitoringType = setRequest.getMonitoringType();
monitoringService.setMonitoring(userId, setRequest.getFileId(), monitoringType);
MonitoringStatusResponse status = monitoringService.getMonitoringStatus(
setRequest.getFileId(), userId);
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Monitoring settings updated",
status);
}
//TODO check tokens for apply
private boolean checkTokensForMonitoring(User user, MonitoringType monitoringType) {
tariffService.getTariffByType(TariffType.valueOf(monitoringType.name()));
return true;
}
}
@@ -0,0 +1,25 @@
package ru.soune.nocopy.repository;
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 ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Repository
public interface FileMonitoringRepository extends JpaRepository<FileMonitoringEntity, String> {
Optional<FileMonitoringEntity> findByFileId(String fileId);
List<FileMonitoringEntity> findByUserIdAndIsActiveTrue(Long userId);
@Query("SELECT fm FROM FileMonitoringEntity fm WHERE fm.isActive = true AND fm.monitoringType != 'NONE' AND fm.nextRun <= :now")
List<FileMonitoringEntity> findReadyForRun(@Param("now") LocalDateTime now);
List<FileMonitoringEntity> findByMonitoringTypeAndIsActiveTrue(MonitoringType type);
}
@@ -0,0 +1,30 @@
package ru.soune.nocopy.repository;
import org.springframework.data.domain.Pageable;
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 ru.soune.nocopy.entity.monitoring.FoundViolationEntity;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface FoundViolationRepository extends JpaRepository<FoundViolationEntity, String> {
List<FoundViolationEntity> findByFileIdOrderByFoundAtDesc(String fileId);
List<FoundViolationEntity> findByUserIdOrderByFoundAtDesc(Long userId);
@Query("SELECT fv FROM FoundViolationEntity fv WHERE fv.userId = :userId AND fv.isNew = true")
List<FoundViolationEntity> findNewViolationsByUser(@Param("userId") Long userId);
@Query("SELECT fv FROM FoundViolationEntity fv WHERE fv.file.id = :fileId AND fv.foundUrl = :foundUrl")
List<FoundViolationEntity> findDuplicate(@Param("fileId") String fileId, @Param("foundUrl") String foundUrl);
List<FoundViolationEntity> findByFoundAtBetween(LocalDateTime start, LocalDateTime end);
@Query("SELECT COUNT(fv) FROM FoundViolationEntity fv WHERE fv.userId = :userId AND fv.isNew = true")
long countNewByUserId(@Param("userId") Long userId);
}
@@ -0,0 +1,47 @@
package ru.soune.nocopy.scheduler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.service.monitoring.MonitoringSearchService;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class MonitoringScheduler {
private final FileMonitoringRepository monitoringRepository;
private final MonitoringSearchService monitoringSearchService;
@Scheduled(cron = "0 1 2 * * *", zone = "Europe/Moscow")
@Transactional
public void processScheduledMonitoring() {
log.info("Starting scheduled monitoring check at 02:01 MSK");
LocalDateTime now = LocalDateTime.now();
List<FileMonitoringEntity> readyForRun = monitoringRepository.findReadyForRun(now);
log.info("Found {} files ready for monitoring", readyForRun.size());
//TODO check tokens for apply
for (FileMonitoringEntity monitoring : readyForRun) {
try {
monitoringSearchService.processFileSearch(monitoring);
log.info("Successfully queued search for file: {}", monitoring.getFile().getId());
} catch (Exception e) {
log.error("Failed to process monitoring for file: {}", monitoring.getFile().getId(), e);
monitoring.setLastRunStatus("ERROR: " + e.getMessage());
monitoringRepository.save(monitoring);
}
}
}
}
@@ -0,0 +1,101 @@
package ru.soune.nocopy.service.monitoring;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.monitoring.MonitoringStatusResponse;
import ru.soune.nocopy.entity.file.*;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.UserNotHavePermission;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.repository.FoundViolationRepository;
import java.io.FileNotFoundException;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;
@Slf4j
@Service
@RequiredArgsConstructor
public class FileMonitoringService {
private final FileMonitoringRepository monitoringRepository;
private final FileEntityRepository fileRepository;
private final FoundViolationRepository violationRepository;
@Transactional
public FileMonitoringEntity setMonitoring(Long userId, String fileId, MonitoringType type) throws FileNotFoundException {
FileEntity file = fileRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException(fileId));
if (!file.getUserId().equals(userId)) {
throw new UserNotHavePermission("User not Have permission for file");
}
FileMonitoringEntity monitoring = monitoringRepository.findByFileId(fileId)
.orElse(FileMonitoringEntity.builder()
.file(file)
.userId(userId)
.build());
monitoring.setMonitoringType(type);
monitoring.setActive(type != MonitoringType.NONE);
monitoring.setNextRun(calculateNextRun(type, LocalDateTime.now()));
monitoring.setUpdatedAt(LocalDateTime.now());
if (monitoring.getCreatedAt() == null) {
monitoring.setCreatedAt(LocalDateTime.now());
}
return monitoringRepository.save(monitoring);
}
@Transactional
public void updateNextRun(FileMonitoringEntity monitoring) {
if (monitoring.isActive() && monitoring.getMonitoringType() != MonitoringType.NONE) {
LocalDateTime nextRun = calculateNextRun(monitoring.getMonitoringType(), LocalDateTime.now());
monitoring.setNextRun(nextRun);
monitoringRepository.save(monitoring);
}
}
private LocalDateTime calculateNextRun(MonitoringType type, LocalDateTime from) {
LocalDateTime nextRun = from.withHour(2).withMinute(0).withSecond(0).withNano(0);
if (from.isAfter(nextRun)) {
nextRun = nextRun.plusDays(1);
}
return switch (type) {
case MONITORING_DAILY -> nextRun;
case MONITORING_WEEKLY -> nextRun.with(TemporalAdjusters.nextOrSame(java.time.DayOfWeek.MONDAY));
case MONITORING_MONTHLY -> nextRun.with(TemporalAdjusters.firstDayOfNextMonth());
case NONE -> null;
};
}
public MonitoringStatusResponse getMonitoringStatus(String fileId, Long userId) {
FileMonitoringEntity monitoring = monitoringRepository.findByFileId(fileId).orElse(null);
FileEntity file = fileRepository.findById(fileId).orElseThrow();
long newViolations = violationRepository.countNewByUserId(userId);
return MonitoringStatusResponse.builder()
.fileId(fileId)
.fileName(file.getOriginalFileName())
.monitoringType(monitoring != null ? monitoring.getMonitoringType() : MonitoringType.NONE)
.nextRun(monitoring != null ? monitoring.getNextRun() : null)
.lastRun(monitoring != null ? monitoring.getLastRun() : null)
.lastRunStatus(monitoring != null ? monitoring.getLastRunStatus() : null)
.isActive(monitoring != null && monitoring.isActive())
.newViolationsCount(newViolations)
.build();
}
}
@@ -0,0 +1,130 @@
package ru.soune.nocopy.service.monitoring;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.entity.file.*;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import ru.soune.nocopy.entity.monitoring.FoundViolationEntity;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
import ru.soune.nocopy.entity.monitoring.SearchEngine;
import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.repository.FoundViolationRepository;
import ru.soune.nocopy.service.search.SearchImageService;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
@Slf4j
@Service
@RequiredArgsConstructor
public class MonitoringSearchService {
private final SearchImageService searchImageService;
private final FoundViolationRepository violationRepository;
private final FileMonitoringRepository monitoringRepository;
@Transactional
public void processFileSearch(FileMonitoringEntity monitoring) {
String searchSessionId = UUID.randomUUID().toString();
log.info("Starting search session: {} for file: {}", searchSessionId, monitoring.getFile().getId());
monitoring.setLastRun(LocalDateTime.now());
monitoring.setLastRunStatus("IN_PROGRESS");
monitoringRepository.save(monitoring);
try {
String yandexResponse = searchImageService.searchReverseByPublicUrl(
monitoring.getFile(), "yandex_reverse_image", "visual_matches");
List<YandexSearchResponse.ImageResult> yandexImages =
searchImageService.getAllImagesWithoutPagination(yandexResponse, "visual_matches");
int savedCount = saveFoundImages(monitoring, yandexImages, SearchEngine.YANDEX, searchSessionId);
try {
String googleResponse = searchImageService.searchReverseByPublicUrl(
monitoring.getFile(), "google_lens", "exact_matches");
List<YandexSearchResponse.ImageResult> googleImages =
searchImageService.getAllImagesWithoutPagination(googleResponse, "exact_matches");
savedCount += saveFoundImages(monitoring, googleImages, SearchEngine.GOOGLE, searchSessionId);
} catch (TimeoutException | IOException e) {
log.warn("Google search failed for session: {}", searchSessionId, e);
}
monitoring.setLastRunStatus("SUCCESS - Found " + savedCount + " violations");
} catch (Exception e) {
log.error("Search failed for session: {}", searchSessionId, e);
monitoring.setLastRunStatus("ERROR: " + e.getMessage());
} finally {
updateNextRun(monitoring);
monitoringRepository.save(monitoring);
}
}
private int saveFoundImages(FileMonitoringEntity monitoring,
List<YandexSearchResponse.ImageResult> images,
SearchEngine engine,
String sessionId) {
int savedCount = 0;
for (YandexSearchResponse.ImageResult image : images) {
List<FoundViolationEntity> existing = violationRepository.findDuplicate(
monitoring.getFile().getId(), image.getUrl());
if (existing.isEmpty()) {
FoundViolationEntity violation = FoundViolationEntity.builder()
.file(monitoring.getFile())
.fileId(monitoring.getFile().getId())
.fileName(monitoring.getFile().getOriginalFileName())
.userId(monitoring.getUserId())
.foundUrl(image.getUrl())
.pageUrl(image.getPageUrl())
.pageTitle(image.getPageTitle())
.host(image.getHost())
.imageWidth(image.getWidth())
.imageHeight(image.getHeight())
.searchEngine(engine)
.searchSessionId(sessionId)
.isNew(true)
.isNotified(false)
.build();
violationRepository.save(violation);
savedCount++;
}
}
return savedCount;
}
private void updateNextRun(FileMonitoringEntity monitoring) {
LocalDateTime nextRun = calculateNextRun(monitoring.getMonitoringType());
monitoring.setNextRun(nextRun);
}
private LocalDateTime calculateNextRun(MonitoringType type) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime nextRun = now.withHour(2).withMinute(0).withSecond(0).withNano(0);
if (now.isAfter(nextRun)) {
nextRun = nextRun.plusDays(1);
}
return switch (type) {
case MONITORING_DAILY -> nextRun;
case MONITORING_WEEKLY -> nextRun.plusWeeks(1);
case MONITORING_MONTHLY -> nextRun.plusMonths(1);
default -> null;
};
}
}
@@ -160,6 +160,10 @@ public class AuthService {
return authTokenRepository.save(genereateAuthToken(user)); return authTokenRepository.save(genereateAuthToken(user));
} }
public AuthToken getAuthToken(String token) {
return authTokenRepository.findByToken(token).get();
}
private AuthToken genereateAuthToken(User user) { private AuthToken genereateAuthToken(User user) {
AuthToken authToken = new AuthToken(); AuthToken authToken = new AuthToken();
authToken.setToken(generateAuthToken()); authToken.setToken(generateAuthToken());
@@ -220,6 +220,37 @@ public class TariffInitializationService {
tariff.setMaxUsers(0L); tariff.setMaxUsers(0L);
tariff.setTariffAccountType("token"); tariff.setTariffAccountType("token");
break; break;
case MONITORING_DAILY:
tariff.setName("monitoring_daily");
tariff.setPrice(0);
tariff.setTokens(10);
tariff.setMaxFilesCount(0);
tariff.setDiskSize(0L);
tariff.setMaxUsers(0L);
tariff.setTariffAccountType("monitoring");
break;
case MONITORING_WEEKLY:
tariff.setName("monitoring_weekly");
tariff.setPrice(0);
tariff.setTokens(25);
tariff.setMaxFilesCount(0);
tariff.setDiskSize(0L);
tariff.setMaxUsers(0L);
tariff.setTariffAccountType("monitoring");
break;
case MONITORING_MONTHLY:
tariff.setName("monitoring_monthly");
tariff.setPrice(0);
tariff.setTokens(50);
tariff.setMaxFilesCount(0);
tariff.setDiskSize(0L);
tariff.setMaxUsers(0L);
tariff.setTariffAccountType("monitoring");
break;
} }
tariffRepository.save(tariff); tariffRepository.save(tariff);