Merge branch 'dev' into NCBACK-33

# Conflicts:
#	src/main/java/ru/soune/nocopy/entity/file/ImageProtection.java
#	src/main/java/ru/soune/nocopy/entity/user/User.java
#	src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java
#	src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
This commit is contained in:
vladp
2026-01-27 05:46:50 +07:00
47 changed files with 1458 additions and 431 deletions
@@ -1,5 +1,8 @@
package ru.soune.nocopy.controller;
import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
@@ -21,6 +24,7 @@ import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.file.UploadStatus;
import ru.soune.nocopy.exception.*;
import ru.soune.nocopy.handler.*;
@@ -29,7 +33,9 @@ import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileUploadService;
import ru.soune.nocopy.util.FileUtil;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -57,6 +63,10 @@ public class ApiController {
private final FileEntityRepository fileEntityRepository;
private final NoCopyFileService noCopyFileService;
private final FileUtil fileUtil;
@PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@PathVariable("version") int version) {
@@ -103,7 +113,8 @@ public class ApiController {
@PathVariable("version") int version,
@RequestParam(value = "upload_id", required = false) String uploadId,
@RequestParam(value = "chunk_number", required = false) Integer chunkNumber,
@RequestParam(value = "chunk", required = false) MultipartFile chunk) {
@RequestParam(value = "chunk", required = false) MultipartFile chunk,
@RequestParam(value = "findSimilar", required = false) Integer findSimilar) {
try {
if (chunk == null || chunk.isEmpty()) {
return buildErrorResponse(uploadId, chunkNumber, "Chunk file null or empty");
@@ -115,39 +126,54 @@ public class ApiController {
return buildErrorResponse(uploadId, chunkNumber, "Valid chunk number is required");
}
fileUploadService.uploadChunk(uploadId, chunkNumber, chunk);
UploadProgressResponse uploadProgressResponse = fileUploadService.uploadChunk(uploadId, chunkNumber,
chunk, findSimilar);
return buildSuccessResponse(uploadId, chunkNumber, chunk);
return buildSuccessResponse(uploadId, chunkNumber, chunk,
fileEntityService.findFileIdByPath(uploadProgressResponse.getFilePath()));
} catch (DuplicateImageException e) {
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
Map<String, Object> duplicateData = new HashMap<>();
duplicateData.put("duplicateFileId", e.duplicateFileId());
duplicateData.put("userId", e.userId());
duplicateData.put("message", e.getMessage());
if (uploadId != null) {
duplicateData.put("uploadId", uploadId);
}
return ResponseEntity.ok().body(new BaseResponse(
20004,
MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
MessageCode.DUPLICATE_FILE_UPLOAD.getDescription(),
Map.of("duplicateOwnerId", e.userId(), "duplicateFileId", e.duplicateFileId())));
duplicateData
));
} catch (Exception e) {
log.error("Error uploading chunk", e);
return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage());
}
}
@GetMapping("/v{version}/files/{fileId}/similar")
@GetMapping("/v{version}/files/{fileId}/similar/{authToken}")
public ResponseEntity<BaseResponse> findSimilarFiles(
@PathVariable("version") int version,
@PathVariable String fileId,
@PathVariable(required = false) String authToken,
@RequestParam(required = false) List<String> similarityLevels,
@PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) {
SimilarityFilter filter = SimilarityFilter.builder()
.similarityLevels(similarityLevels)
.build();
Page<SimilarFileResponse> similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable);
Page<SimilarFileDTO> similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable, authToken);
String messageDesc;
MessageCode success;
if (similarFiles.isEmpty()) {
messageDesc = MessageCode.FILE_NOT_FOUND.getDescription();
success = MessageCode.FILE_NOT_FOUND;
success = MessageCode.SUCCESS;
} else {
messageDesc = MessageCode.SIMILAR_FILES_FOUND.getDescription();
success = MessageCode.SIMILAR_FILES_FOUND;
success = MessageCode.SUCCESS;
}
Map<String, Object> responseData = new HashMap<>();
@@ -193,7 +219,8 @@ public class ApiController {
.uploadId(uploadId)
.build();
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
return ResponseEntity.ok().body(new BaseResponse(
20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Failed to get upload progress: " + e.getMessage(), responseBody));
}
}
@@ -352,6 +379,45 @@ public class ApiController {
}
}
@GetMapping("/protect/{fileId}")
public ResponseEntity<?> protect( @PathVariable(required = false) String fileId) {
Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
if (!optionalFileEntity.isPresent()) {
return ResponseEntity.notFound().build();
}
FileEntity fileEntity = optionalFileEntity.get();
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity);
noCopyFileService.addFile(fileInfo);
fileEntity.setProtectionStatus(ProtectionStatus.PROCESSING);
fileEntityRepository.save(fileEntity);
return ResponseEntity.ok().build();
}
@GetMapping("/check/{fileId}/{type}")
public ResponseEntity<?> check(@PathVariable(required = false) String fileId,
@PathVariable(required = false) String type) {
Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
if (!optionalFileEntity.isPresent()) {
return ResponseEntity.notFound().build();
}
FileEntity fileEntity = optionalFileEntity.get();
Path path = type.toLowerCase().equals("image") ? Paths.get(fileEntity.getFilePath()) :
Paths.get(fileEntity.getProtectedFilePath());
File file = path.toFile();
NoCopyCheckResult noCopyCheckResult = noCopyFileService.checkFile(file,
FileProtector.Type.valueOf(type.toUpperCase()));
return ResponseEntity.ok().body(noCopyCheckResult);
}
private ResponseEntity<BaseResponse> checkForDuplicates(String uploadId) throws IOException {
Optional<FileEntity> uploadedFile = fileEntityRepository.findByUploadSessionId(uploadId);
@@ -360,7 +426,7 @@ public class ApiController {
}
FileEntity fileEntity = uploadedFile.get();
List<SimilarFileResponse> similarFiles = fileSimilarityService.findSimilarFiles(fileEntity.getId());
List<SimilarFileDTO> similarFiles = fileSimilarityService.findSimilarFiles(fileEntity.getId());
if (hasDuplicate(similarFiles)) {
return handleDuplicate(fileEntity, similarFiles);
@@ -369,11 +435,11 @@ public class ApiController {
return null;
}
private boolean hasDuplicate(List<SimilarFileResponse> similarFiles) {
private boolean hasDuplicate(List<SimilarFileDTO> similarFiles) {
return similarFiles.stream().anyMatch(f -> f.getHammingDistance() <= 5);
}
private ResponseEntity<BaseResponse> handleDuplicate(FileEntity fileEntity, List<SimilarFileResponse> similarFiles)
private ResponseEntity<BaseResponse> handleDuplicate(FileEntity fileEntity, List<SimilarFileDTO> similarFiles)
throws IOException {
fileEntityService.deleteFromDisk(fileEntity);
@@ -396,11 +462,13 @@ public class ApiController {
return null;
}
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk) {
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk,
String fileId) {
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.chunkSize(chunk.getSize())
.fileId(fileId)
.message("Chunk uploaded successfully")
.build();
@@ -459,16 +527,4 @@ public class ApiController {
}
return contentType;
}
private boolean isPreviewSupported(String mimeType) {
if (mimeType == null) {
return false;
}
return mimeType.startsWith("image") ||
mimeType.startsWith("text") ||
mimeType.equals("pdf") ||
mimeType.startsWith("video") ||
mimeType.startsWith("audio");
}
}
@@ -3,6 +3,9 @@ package ru.soune.nocopy.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("check/api")
public class HealtCheckController {
@@ -11,4 +14,22 @@ public class HealtCheckController {
public HttpStatus healtCheck() {
return HttpStatus.OK;
}
@GetMapping("/api/debug/memory")
public Map<String, String> getMemoryInfo() {
Runtime runtime = Runtime.getRuntime();
long mb = 1024 * 1024;
Map<String, String> info = new HashMap<>();
info.put("maxMemory (MB)", String.valueOf(runtime.maxMemory() / mb));
info.put("totalMemory (MB)", String.valueOf(runtime.totalMemory() / mb));
info.put("freeMemory (MB)", String.valueOf(runtime.freeMemory() / mb));
info.put("usedMemory (MB)", String.valueOf((runtime.totalMemory() - runtime.freeMemory()) / mb));
info.put("availableProcessors", String.valueOf(runtime.availableProcessors()));
info.put("JAVA_TOOL_OPTIONS", System.getenv("JAVA_TOOL_OPTIONS"));
info.put("JAVA_OPTS", System.getenv("JAVA_OPTS"));
return info;
}
}