diff --git a/src/main/java/ru/soune/nocopy/controller/ApiController.java b/src/main/java/ru/soune/nocopy/controller/ApiController.java index 652fab7..9ecad18 100644 --- a/src/main/java/ru/soune/nocopy/controller/ApiController.java +++ b/src/main/java/ru/soune/nocopy/controller/ApiController.java @@ -2,6 +2,10 @@ package ru.soune.nocopy.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @@ -13,13 +17,22 @@ import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.RegAnswer; import ru.soune.nocopy.dto.file.ChunkUploadResponse; import ru.soune.nocopy.dto.file.CompleteUploadResponse; +import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.dto.file.UploadProgress; +import ru.soune.nocopy.entity.AuthToken; import ru.soune.nocopy.entity.file.UploadStatus; +import ru.soune.nocopy.exception.NotFoundAuthToken; import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.ValidationException; import ru.soune.nocopy.handler.*; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileUploadService; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,6 +47,9 @@ public class ApiController { private final Map handlers; + private final FileEntityService fileEntityService; + + private final AuthTokenRepository authTokenRepository; @PostMapping("/v{version}/data") public ResponseEntity handlePostRequest(@RequestBody BaseRequest request, @@ -219,6 +235,117 @@ public class ApiController { } } + + @GetMapping("/v{version}/files/download/{fileId}") + public ResponseEntity downloadFile( + @PathVariable String fileId, + @PathVariable Integer version, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId, version); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + if (!fileInfo.isExistsOnDisk()) { + return ResponseEntity.status(404) + .body(null); + } + + Path filePath = Paths.get(fileInfo.getFilePath()); + Resource resource = new UrlResource(filePath.toUri()); + + if (!resource.exists()) { + return ResponseEntity.status(404).build(); + } + + String contentType = determineContentType(filePath); + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + fileInfo.getOriginalFileName() + "\"") + .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileInfo.getFileSize())) + .body(resource); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error downloading file", e); + return ResponseEntity.status(500).build(); + } + } + + @GetMapping("/v{version}/files/info/{fileId}") + public ResponseEntity getFileInfo( + @PathVariable String fileId, + @PathVariable Integer version, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId, version); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + return ResponseEntity.ok(fileInfo); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error getting file info", e); + return ResponseEntity.status(500).build(); + } + } + + @GetMapping("/v{version}/files/preview/{fileId}") + public ResponseEntity previewFile( + @PathVariable String fileId, + @PathVariable Integer version, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId, version); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + if (!isPreviewSupported(fileInfo.getMimeType())) { + return ResponseEntity.status(415) + .body(null); + } + + Path filePath = Paths.get(fileInfo.getFilePath()); + Resource resource = new UrlResource(filePath.toUri()); + + if (!resource.exists()) { + return ResponseEntity.status(404).build(); + } + + String contentType = determineContentType(filePath); + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, + "inline; filename=\"" + fileInfo.getOriginalFileName() + "\"") + .body(resource); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error previewing file", e); + return ResponseEntity.status(500).build(); + } + } + + private ResponseEntity createValidationErrorResponse(BindingResult bindingResult, Integer msgId) { List> fieldErrors = bindingResult.getFieldErrors() .stream() @@ -245,4 +372,32 @@ public class ApiController { return errorDetail; } + + + private String determineContentType(Path filePath) throws IOException { + String contentType = Files.probeContentType(filePath); + if (contentType == null) { + contentType = "application/octet-stream"; + } + return contentType; + } + + private boolean isPreviewSupported(String mimeType) { + if (mimeType == null) { + return false; + } + + return mimeType.startsWith("image/") || + mimeType.startsWith("text/") || + mimeType.equals("application/pdf") || + mimeType.startsWith("video/") || + mimeType.startsWith("audio/"); + } + + private Long getUserIdFromToken(String tokenHeader) { + String token = tokenHeader.replace("Bearer ", ""); + AuthToken authToken = authTokenRepository.findByToken(token) + .orElseThrow(() -> new NotFoundAuthToken("Token not found")); + return authToken.getUser().getId(); + } } \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java b/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java deleted file mode 100644 index 8678932..0000000 --- a/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java +++ /dev/null @@ -1,164 +0,0 @@ -package ru.soune.nocopy.controller.file; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import ru.soune.nocopy.dto.file.FileEntityResponse; -import ru.soune.nocopy.entity.AuthToken; -import ru.soune.nocopy.exception.NotFoundAuthToken; -import ru.soune.nocopy.repository.AuthTokenRepository; -import ru.soune.nocopy.service.file.FileEntityService; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -@Slf4j -@RestController -@RequestMapping("/api/files") -@RequiredArgsConstructor -public class FileDownloadController { - - private final FileEntityService fileEntityService; - - private final AuthTokenRepository authTokenRepository; - - @GetMapping("/download/{fileId}") - public ResponseEntity downloadFile( - @PathVariable String fileId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getById(fileId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403).build(); - } - - if (!fileInfo.isExistsOnDisk()) { - return ResponseEntity.status(404) - .body(null); - } - - Path filePath = Paths.get(fileInfo.getFilePath()); - Resource resource = new UrlResource(filePath.toUri()); - - if (!resource.exists()) { - return ResponseEntity.status(404).build(); - } - - String contentType = determineContentType(filePath); - - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(contentType)) - .header(HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"" + fileInfo.getOriginalFileName() + "\"") - .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileInfo.getFileSize())) - .body(resource); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401).build(); - } catch (Exception e) { - log.error("Error downloading file", e); - return ResponseEntity.status(500).build(); - } - } - - @GetMapping("/preview/{fileId}") - public ResponseEntity previewFile( - @PathVariable String fileId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getById(fileId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403).build(); - } - - if (!isPreviewSupported(fileInfo.getMimeType())) { - return ResponseEntity.status(415) - .body(null); - } - - Path filePath = Paths.get(fileInfo.getFilePath()); - Resource resource = new UrlResource(filePath.toUri()); - - if (!resource.exists()) { - return ResponseEntity.status(404).build(); - } - - String contentType = determineContentType(filePath); - - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(contentType)) - .header(HttpHeaders.CONTENT_DISPOSITION, - "inline; filename=\"" + fileInfo.getOriginalFileName() + "\"") - .body(resource); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401).build(); - } catch (Exception e) { - log.error("Error previewing file", e); - return ResponseEntity.status(500).build(); - } - } - - @GetMapping("/info/{fileId}") - public ResponseEntity getFileInfo( - @PathVariable String fileId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getById(fileId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403).build(); - } - - return ResponseEntity.ok(fileInfo); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401).build(); - } catch (Exception e) { - log.error("Error getting file info", e); - return ResponseEntity.status(500).build(); - } - } - - private String determineContentType(Path filePath) throws IOException { - String contentType = Files.probeContentType(filePath); - if (contentType == null) { - contentType = "application/octet-stream"; - } - return contentType; - } - - private boolean isPreviewSupported(String mimeType) { - if (mimeType == null) { - return false; - } - - return mimeType.startsWith("image/") || - mimeType.startsWith("text/") || - mimeType.equals("application/pdf") || - mimeType.startsWith("video/") || - mimeType.startsWith("audio/"); - } - - private Long getUserIdFromToken(String tokenHeader) { - String token = tokenHeader.replace("Bearer ", ""); - AuthToken authToken = authTokenRepository.findByToken(token) - .orElseThrow(() -> new NotFoundAuthToken("Token not found")); - return authToken.getUser().getId(); - } -} diff --git a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java index 5c1b3d0..bbc8da9 100644 --- a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java @@ -68,7 +68,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = getUserIdFromToken(fileRequest.getToken()); - FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId()); + FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), @@ -99,7 +99,8 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = getUserIdFromToken(fileRequest.getToken()); - FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId()); + FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId(), + request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), @@ -133,7 +134,7 @@ public class FileEntityHandler implements RequestHandler { int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; - FileResponse files = fileEntityService.getUserFiles(userId, page, pageSize); + FileResponse files = fileEntityService.getUserFiles(userId, page, pageSize, request.getVersion()); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), @@ -164,7 +165,7 @@ public class FileEntityHandler implements RequestHandler { return handleGetUserFiles(request, fileRequest); } - FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000); + FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000, request.getVersion()); var filteredFiles = allFiles.getFiles().stream() .filter(f -> f.getOriginalFileName().toLowerCase().contains(fileRequest.getQuery().toLowerCase())) @@ -234,7 +235,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = getUserIdFromToken(fileRequest.getToken()); - FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId()); + FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), diff --git a/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java b/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java index 10737f7..f40487a 100644 --- a/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java +++ b/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java @@ -55,12 +55,5 @@ public class FileUploadRequestValidator implements Validator { errors.rejectValue("extension", "extension.required", "Extension is required"); return; } - - if (!extension.startsWith(".")) { - errors.rejectValue("extension", "extension.invalid.format", - "Extension must start with '.'"); - return; - } } - -} +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java index 7bd858e..8b239a5 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java @@ -79,38 +79,38 @@ public class FileEntityService { } @Transactional(readOnly = true) - public FileEntityResponse getById(String fileId) { + public FileEntityResponse getById(String fileId, int version) { FileEntity fileEntity = fileEntityRepository.findById(fileId) .orElseThrow(() -> new FileEntityNotFoundException(fileId)); - return convertToResponse(fileEntity); + return convertToResponse(fileEntity, version); } @Transactional(readOnly = true) - public FileEntityResponse getByUploadSessionId(String uploadSessionId) { + public FileEntityResponse getByUploadSessionId(String uploadSessionId, int version) { FileEntity fileEntity = fileEntityRepository.findByUploadSessionId(uploadSessionId) .orElseThrow(() -> new FileEntityNotFoundException( "Not found for upload session: " + uploadSessionId)); - return convertToResponse(fileEntity); + return convertToResponse(fileEntity, version); } @Transactional(readOnly = true) - public FileEntityResponse getByFilePath(String filePath) { + public FileEntityResponse getByFilePath(String filePath, int version) { FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath) .orElseThrow(() -> new FileEntityNotFoundException("Path: " + filePath)); - return convertToResponse(fileEntity); + return convertToResponse(fileEntity, version); } @Transactional(readOnly = true) - public FileResponse getAllUserFiles(Long userId) { + public FileResponse getAllUserFiles(Long userId, int version) { List fileEntities = fileEntityRepository.findByUserIdAndStatus( userId, FileStatus.ACTIVE); List files = fileEntities.stream() - .map(this::convertToResponse) + .map(file -> convertToResponse(file, version)) .collect(Collectors.toList()); long totalSize = fileEntities.stream() @@ -129,7 +129,7 @@ public class FileEntityService { @Transactional(readOnly = true) - public FileResponse getUserFiles(Long userId, int page, int pageSize) { + public FileResponse getUserFiles(Long userId, int page, int pageSize, int version) { List allFiles = fileEntityRepository.findByUserIdAndStatus( userId, FileStatus.ACTIVE); @@ -150,7 +150,7 @@ public class FileEntityService { List pageFiles = allFiles.subList(start, end); List files = pageFiles.stream() - .map(this::convertToResponse) + .map(file -> convertToResponse(file, version)) .collect(Collectors.toList()); long totalSize = allFiles.stream() @@ -185,16 +185,6 @@ public class FileEntityService { return totalSize != null ? totalSize : 0L; } - @Transactional(readOnly = true) - public List searchFiles(Long userId, String query) { - List files = fileEntityRepository.searchByFileName(userId, query); - - return files.stream() - .filter(f -> f.getStatus() == FileStatus.ACTIVE) - .map(this::convertToResponse) - .collect(Collectors.toList()); - } - private boolean checkFileExistsOnDisk(String filePath) { try { return Files.exists(Paths.get(filePath)); @@ -204,7 +194,7 @@ public class FileEntityService { } } - private FileEntityResponse convertToResponse(FileEntity fileEntity) { + private FileEntityResponse convertToResponse(FileEntity fileEntity, int version) { boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath()); return FileEntityResponse.builder() @@ -222,7 +212,7 @@ public class FileEntityService { .createdAt(fileEntity.getCreatedAt()) .updatedAt(fileEntity.getUpdatedAt()) .formattedSize(formatFileSize(fileEntity.getFileSize())) - .downloadUrl("/api/files/download/" + fileEntity.getId()) + .downloadUrl("/api/v" + version + "/files/download/" + fileEntity.getId()) .existsOnDisk(existsOnDisk) .build(); }