Merge branch 'dev' into NCBACK-35

# Conflicts:
#	README.md
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java
#	src/main/java/ru/soune/nocopy/service/file/FileCleanupService.java
#	src/main/java/ru/soune/nocopy/service/file/FileEntityService.java
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
#	src/main/java/ru/soune/nocopy/service/file/impl/ProtectionFileProviderImpl.java
This commit is contained in:
vladp
2026-03-31 14:24:03 +07:00
229 changed files with 10416 additions and 757 deletions
@@ -2,19 +2,20 @@ package ru.soune.nocopy.service.file;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.file.FileEntityResponse;
import ru.soune.nocopy.dto.file.FileResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.FileUploadSession;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
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.repository.FileEntityRepository;
import ru.soune.nocopy.repository.SimilarImageProjection;
import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.ImageHashService;
@@ -27,7 +28,7 @@ import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@@ -37,70 +38,14 @@ public class FileEntityService {
private final FileEntityRepository fileEntityRepository;
private final ImageHashService imageHashService;
private final FileSimilarityService fileSimilarityService;
private final UserRepository userRepository;
private final ImageResizeService imageResizeService;
private final CloudStorageService cloudStorageService;
@Transactional(noRollbackFor = DuplicateImageException.class)
public FileEntity createFromUploadSession(FileUploadSession session, String checksum) {
log.info("Creating FileEntity for upload session: {}", session.getUploadId());
try {
Path filePath = Paths.get(session.getFilePath());
if (!Files.exists(filePath)) {
throw new IOException("File not found on disk: " + filePath);
}
Map<String, Long> imageHash = Map.of();
if (!session.getFileType().startsWith("video")) {
imageHash = imageHashService.calculateHash(filePath);
List<SimilarImageProjection> duplicatedByHash = fileSimilarityService.findDuplicatedByHash(
imageHash.get("hi"), imageHash.get("low"));
if (!duplicatedByHash.isEmpty()) {
SimilarImageProjection similarImageProjection = duplicatedByHash.get(0);
throw new DuplicateImageException("Duplicate", similarImageProjection.getId(),
similarImageProjection.getUserId());
}
}
long fileSize = Files.size(filePath);
String originalName = session.getFileName();
String storedName = filePath.getFileName().toString();
FileEntity fileEntity = FileEntity.builder()
.userId(session.getUserId())
.originalFileName(originalName)
.storedFileName(storedName)
.filePath(session.getFilePath())
.fileSize(fileSize)
.mimeType(session.getFileType())
.fileExtension(session.getExtension())
.checksum(checksum)
.uploadSessionId(session.getUploadId())
.status(FileStatus.ACTIVE)
.build();
FileEntity saved = fileEntityRepository.save(fileEntity);
if (!imageHash.isEmpty()) {
imageHashService.create(saved, imageHash);
}
return saved;
} catch (IOException e) {
log.error("Failed to create FileEntity for session {}: {}",
session.getUploadId(), e.getMessage(), e);
throw new RuntimeException("Failed to create file entity: " + e.getMessage(), e);
}
}
@Value("${server.baseurl}")
private String baseUrl;
@Transactional(readOnly = true)
public FileEntityResponse getById(String fileId, int version) {
@@ -119,6 +64,34 @@ public class FileEntityService {
return convertToResponse(fileEntity, version);
}
@Transactional(readOnly = true)
public FileEntityResponse getByFilePath(String filePath, int version) {
FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath)
.orElseThrow(() -> new FileEntityNotFoundException("Path: " + filePath));
return convertToResponse(fileEntity, version);
}
@Transactional(readOnly = true)
public List<FileEntity> getAllUserFiles(Long userId) {
User user = userRepository.findById(userId).orElseThrow();
List<FileEntity> allFiles = new ArrayList<>();
if (user.getCompany() != null) {
for (Long uId : userRepository.findByCompanyId(user.getCompany().getId()).stream()
.map(User::getId)
.toList()) {
allFiles.addAll(fileEntityRepository.findByUserIdAndStatusIn(uId,
List.of(FileStatus.ACTIVE, FileStatus.BLOCKED)));
}
} else {
allFiles = fileEntityRepository.findByUserIdAndStatusIn(userId,
List.of(FileStatus.ACTIVE, FileStatus.BLOCKED));
}
return allFiles;
}
@Transactional(readOnly = true)
public FileResponse getUserFiles(Long userId, int page, int pageSize, int version) {
User user = userRepository.findById(userId).orElseThrow();
@@ -128,12 +101,13 @@ public class FileEntityService {
for (Long uId : userRepository.findByCompanyId(user.getCompany().getId()).stream()
.map(User::getId)
.toList()) {
allFiles.addAll(fileEntityRepository.findByUserIdAndStatus(uId, FileStatus.ACTIVE));
allFiles.addAll(fileEntityRepository.findByUserIdAndStatusIn(uId,
List.of(FileStatus.ACTIVE, FileStatus.BLOCKED)));
}
} else {
allFiles = fileEntityRepository.findByUserIdAndStatus(
userId, FileStatus.ACTIVE);
allFiles = fileEntityRepository.findByUserIdAndStatusIn(userId,
List.of(FileStatus.ACTIVE, FileStatus.BLOCKED));
}
int start = (page - 1) * pageSize;
@@ -175,13 +149,13 @@ public class FileEntityService {
fileEntity.setImageHash(null);
}
fileEntity.setStatus(FileStatus.DELETED);
fileEntity.setStatus(FileStatus.REMOVED);
fileEntity.setUpdatedAt(LocalDateTime.now());
fileEntity.setProtectionStatus(ProtectionStatus.NOT_PROTECTED);
// fileEntity.setProtectionStatus(ProtectionStatus.NOT_PROTECTED);
Path path = Paths.get(fileEntity.getProtectedFilePath());
Files.deleteIfExists(path);
fileEntity.setProtectedFilePath("");
// Path path = Paths.get(fileEntity.getProtectedFilePath());
// Files.deleteIfExists(path);
// fileEntity.setProtectedFilePath("");
fileEntityRepository.save(fileEntity);
}
@@ -196,7 +170,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);
@@ -204,6 +178,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 FileEntity writeProtectedFile(String id, byte[] data, String fileExt) throws IOException {
FileEntity fileEntity = fileEntityRepository.findById(id)
@@ -218,6 +200,10 @@ public class FileEntityService {
Files.write(protectedFilePath, data);
if (imageResizeService.isImage(extension)) {
imageResizeService.generateSizes(fileEntity, data);
}
fileEntity.setProtectedFilePath(protectedFilePath.toString());
fileEntity.setProtectedAt(LocalDateTime.now());
fileEntity.setUpdatedAt(LocalDateTime.now());
@@ -308,14 +294,29 @@ public class FileEntityService {
}
}
private final FileMonitoringRepository fileMonitoringRepository;
private FileEntityResponse convertToResponse(FileEntity fileEntity, int version) {
boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath());
User user = userRepository.findById(fileEntity.getUserId()).get();
String fileName = fileEntity.getOriginalFileName();
String name = fileName;
Optional<FileMonitoringEntity> fileMonitoringRepositoryByFileId =
fileMonitoringRepository.findByFileId(fileEntity.getId());
String monitoring = fileMonitoringRepositoryByFileId.map(fileMonitoringEntity ->
fileMonitoringEntity.getMonitoringType().name())
.orElseGet(MonitoringType.NONE::name);
int lastDotIndex = fileName.lastIndexOf(".");
if (lastDotIndex > 0) {
name = fileName.substring(0, lastDotIndex);
}
return FileEntityResponse.builder()
.id(fileEntity.getId())
.userId(fileEntity.getUserId())
.originalFileName(fileEntity.getOriginalFileName())
.originalFileName(name + "." + fileEntity.getFileExtension())
.storedFileName(fileEntity.getStoredFileName())
.filePath(fileEntity.getProtectedFilePath())
.fileSize(fileEntity.getFileSize())
@@ -331,16 +332,16 @@ public class FileEntityService {
.existsOnDisk(existsOnDisk)
.supportId(fileEntity.getSupportId())
.protectStatus(fileEntity.getProtectionStatus().toString())
.fileName(fileEntity.getOriginalFileName().replace("." +
fileEntity.getFileExtension(), "") + "_nocopy_protected" +
"." + fileEntity.getFileExtension())
.fileName(name + "_nocopy_protected" + "." + fileEntity.getFileExtension())
.ownerName(user.getFullName())
.ownerEmail(user.getEmail())
.ownerCompany(user.getCompanyName())
.monitoring(monitoring)
//TODO fix after add logic for check for protect file
.checksCount(0)
.fileUploadDate(fileEntity.getCreatedAt())
.protectedFilePath(fileEntity.getProtectedFilePath())
.thumbnailFileUrl(baseUrl + "/api/files/protected/" + fileEntity.getId() + "/thumbnail")
.build();
}
}