dev add new status fo file
Test Workflow / test (push) Successful in 3s

This commit is contained in:
vladp
2026-03-27 13:37:37 +07:00
parent a01adfc848
commit 57dcc12cf2
17 changed files with 932 additions and 6 deletions
@@ -16,7 +16,6 @@ import ru.soune.nocopy.entity.monitoring.MonitoringType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.DuplicateImageException;
import ru.soune.nocopy.exception.FileEntityNotFoundException;
import ru.soune.nocopy.handler.MonitoringHandler;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.repository.SimilarImageProjection;
@@ -252,7 +251,7 @@ public class FileEntityService {
return totalSize != null ? totalSize : 0L;
}
public void changeStatus(ProtectionStatus newStatus, String fileId) {
public void changeProtectionStatus(ProtectionStatus newStatus, String fileId) {
FileEntity fileEntity = fileEntityRepository.findById(fileId)
.orElseThrow(() -> new FileEntityNotFoundException(fileId));
fileEntity.setProtectionStatus(newStatus);
@@ -260,6 +259,14 @@ public class FileEntityService {
fileEntityRepository.save(fileEntity);
}
public void changeFileStatus(FileStatus newStatus, String fileId) {
FileEntity fileEntity = fileEntityRepository.findById(fileId)
.orElseThrow(() -> new FileEntityNotFoundException(fileId));
fileEntity.setStatus(newStatus);
fileEntityRepository.save(fileEntity);
}
@Transactional
public void writeProtectedFile(String id, byte[] data, String fileExt) throws IOException {
FileEntity fileEntity = fileEntityRepository.findById(id)
@@ -16,21 +16,21 @@ public class NoCopyProcessingListenerImpl implements FileProtector.ProcessingLis
@Override
public void onStartProcessing(FileProtector.FileInfo fileInfo) {
fileEntityService.changeStatus(ProtectionStatus.PROCESSING, fileInfo.getId());
fileEntityService.changeProtectionStatus(ProtectionStatus.PROCESSING, fileInfo.getId());
}
@Override
public void onProcessingFailed(FileProtector.FileInfo fileInfo) {
fileEntityService.changeStatus(ProtectionStatus.FAILED, fileInfo.getId());
fileEntityService.changeProtectionStatus(ProtectionStatus.FAILED, fileInfo.getId());
}
@Override
public void onSavingFailed(FileProtector.FileInfo fileInfo) {
fileEntityService.changeStatus(ProtectionStatus.FAILED_SAVE, fileInfo.getId());
fileEntityService.changeProtectionStatus(ProtectionStatus.FAILED_SAVE, fileInfo.getId());
}
@Override
public void onFinish(FileProtector.FileInfo fileInfo) {
fileEntityService.changeStatus(ProtectionStatus.PROTECTED, fileInfo.getId());;
fileEntityService.changeProtectionStatus(ProtectionStatus.PROTECTED, fileInfo.getId());;
}
}
@@ -0,0 +1,236 @@
package ru.soune.nocopy.service.file.moderation;
import lombok.RequiredArgsConstructor;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.entity.file.*;
import ru.soune.nocopy.entity.file.moderation.ModerationLog;
import ru.soune.nocopy.exception.FileEntityNotFoundException;
import ru.soune.nocopy.exception.InvalidAppealException;
import ru.soune.nocopy.repository.FileAppealRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.ModerationLogRepository;
import ru.soune.nocopy.service.file.FileEntityService;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ModerationService {
private final FileEntityRepository fileEntityRepository;
private final FileAppealRepository fileAppealRepository;
private final ModerationLogRepository moderationLogRepository;
private final FileEntityService fileEntityService;
private static final List<FileStatus> MODERATION_NEEDED_STATUSES = Arrays.asList(
FileStatus.MODERATION,
FileStatus.BLOCKED
);
/**
* Модерация файла администратором
*/
@Transactional
public void moderateFile(ModerationActionRequest request, Long moderatorId) {
FileEntity file = fileEntityRepository.findById(request.getFileId())
.orElseThrow(() -> new FileEntityNotFoundException(request.getFileId()));
if (!MODERATION_NEEDED_STATUSES.contains(file.getStatus())) {
throw new IllegalStateException("File is not in moderation state. Current status: " + file.getStatus());
}
ModerationLog log = ModerationLog.builder()
.fileId(file.getId())
.moderatorId(moderatorId)
.oldStatus(file.getStatus())
.newStatus(request.getNewStatus())
.reason(request.getComment())
.moderationReasonId(request.getModerationReasonId())
.comment(request.getComment())
.build();
moderationLogRepository.save(log);
file.setStatus(request.getNewStatus());
file.setModerationReasonId(request.getModerationReasonId());
fileEntityRepository.save(file);
if (request.getNewStatus() == FileStatus.BLOCKED ||
request.getNewStatus() == FileStatus.VIOLATION) {
fileAppealRepository.findByFileIdAndStatus(file.getId(), AppealStatus.PENDING)
.ifPresent(appeal -> {
appeal.setStatus(AppealStatus.REJECTED);
appeal.setAdminComment("Апелляция отклонена в процессе модерации");
appeal.setResolvedAt(LocalDateTime.now());
fileAppealRepository.save(appeal);
});
}
}
/**
* Подача апелляции пользователем
*/
@Transactional
public AppealResponse submitAppeal(AppealRequest request, Long userId) {
FileEntity file = fileEntityRepository.findById(request.getFileId())
.orElseThrow(() -> new FileEntityNotFoundException(request.getFileId()));
if (!file.getUserId().equals(userId)) {
throw new SecurityException("User is not the owner of this file");
}
if (!canAppeal(file)) {
throw new InvalidAppealException("Cannot appeal this file. Current status: " + file.getStatus());
}
if (hasActiveAppeal(file.getId())) {
throw new InvalidAppealException("Active appeal already exists for this file");
}
FileAppeal appeal = FileAppeal.builder()
.fileId(file.getId())
.userId(userId)
.appealReason(request.getAppealReason())
.additionalInfo(request.getAdditionalInfo())
.status(AppealStatus.PENDING)
.moderationBeforeStatus(file.getStatus())
.moderationReasonId(file.getModerationReasonId())
.build();
FileAppeal saved = fileAppealRepository.save(appeal);
return convertToResponse(saved);
}
/**
* Рассмотрение апелляции администратором
*/
@Transactional
public void reviewAppeal(String appealId, boolean approve, String comment, Long moderatorId) {
FileAppeal appeal = fileAppealRepository.findById(appealId)
.orElseThrow(() -> new RuntimeException("Appeal not found: " + appealId));
if (appeal.getStatus() != AppealStatus.PENDING) {
throw new IllegalStateException("Appeal already processed");
}
FileEntity file = fileEntityRepository.findById(appeal.getFileId())
.orElseThrow(() -> new FileEntityNotFoundException(appeal.getFileId()));
appeal.setStatus(approve ? AppealStatus.APPROVED : AppealStatus.REJECTED);
appeal.setAdminComment(comment);
appeal.setModeratorId(moderatorId);
appeal.setResolvedAt(LocalDateTime.now());
if (approve) {
file.setStatus(FileStatus.ACTIVE);
file.setModerationReasonId(null);
}
ModerationLog log = ModerationLog.builder()
.fileId(file.getId())
.moderatorId(moderatorId)
.oldStatus(file.getStatus())
.newStatus(file.getStatus())
.comment("Appeal review: " + comment)
.build();
moderationLogRepository.save(log);
fileEntityRepository.save(file);
fileAppealRepository.save(appeal);
}
/**
* Проверка, можно ли подать апелляцию на файл
*/
public boolean canAppeal(FileEntity file) {
if (file.getStatus() != FileStatus.BLOCKED) {
return false;
}
return true;
}
/**
* Проверка наличия активной апелляции
*/
public boolean hasActiveAppeal(String fileId) {
return fileAppealRepository.existsByFileIdAndStatusIn(
fileId,
Arrays.asList(AppealStatus.PENDING, AppealStatus.IN_REVIEW)
);
}
/**
* Получение информации о файле для модерации
*/
@Transactional(readOnly = true)
public FileModerationInfo getFileModerationInfo(String fileId) {
FileEntity file = fileEntityRepository.findById(fileId)
.orElseThrow(() -> new FileEntityNotFoundException(fileId));
FileAppeal activeAppeal = fileAppealRepository.findByFileIdAndStatus(
fileId, AppealStatus.PENDING).orElse(null);
return FileModerationInfo.builder()
.fileId(file.getId())
.fileName(file.getOriginalFileName())
.currentStatus(file.getStatus())
.uploadDate(file.getCreatedAt())
.hasActiveAppeal(activeAppeal != null)
.activeAppeal(activeAppeal != null ?
AppealInfo.builder()
.appealId(activeAppeal.getId())
.status(activeAppeal.getStatus().name())
.createdAt(activeAppeal.getCreatedAt())
.build() : null)
.build();
}
/**
* Получение списка файлов на модерации
*/
@Transactional(readOnly = true)
public Page<FileEntity> getFilesForModeration(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
return fileEntityRepository.findByStatusIn(
Arrays.asList(FileStatus.MODERATION, FileStatus.BLOCKED),
pageable
);
}
/**
* Получение апелляций пользователя
*/
@Transactional(readOnly = true)
public Page<AppealResponse> getUserAppeals(Long userId, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
return fileAppealRepository.findByUserId(userId, pageable)
.map(this::convertToResponse);
}
private AppealResponse convertToResponse(FileAppeal appeal) {
return AppealResponse.builder()
.appealId(appeal.getId())
.fileId(appeal.getFileId())
.appealReason(appeal.getAppealReason())
.additionalInfo(appeal.getAdditionalInfo())
.status(appeal.getStatus().name())
.adminComment(appeal.getAdminComment())
.createdAt(appeal.getCreatedAt())
.resolvedAt(appeal.getResolvedAt())
.build();
}
}