This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user