# Conflicts: # docker-compose.yaml # src/main/java/ru/soune/nocopy/dto/MessageCode.java
This commit is contained in:
@@ -7,11 +7,17 @@ import org.springframework.stereotype.Component;
|
||||
import ru.soune.nocopy.dto.*;
|
||||
import ru.soune.nocopy.dto.file.*;
|
||||
import ru.soune.nocopy.entity.AuthToken;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.entity.file.FileStatus;
|
||||
import ru.soune.nocopy.exception.FileEntityNotFoundException;
|
||||
import ru.soune.nocopy.exception.NotFoundAuthToken;
|
||||
import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.service.file.FileEntityService;
|
||||
import ru.soune.nocopy.service.file.FileStatsService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -19,9 +25,15 @@ import java.util.Arrays;
|
||||
public class FileEntityHandler implements RequestHandler {
|
||||
|
||||
private final FileEntityService fileEntityService;
|
||||
|
||||
private final FileStatsService fileStatsService;
|
||||
|
||||
private final AuthTokenRepository authTokenRepository;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) {
|
||||
try {
|
||||
@@ -31,15 +43,17 @@ public class FileEntityHandler implements RequestHandler {
|
||||
String action = fileRequest.getAction();
|
||||
|
||||
switch (action) {
|
||||
case "get_file_info":
|
||||
case "file_info":
|
||||
return handleGetFileInfo(request, fileRequest);
|
||||
case "get_file_by_session":
|
||||
case "user_files_info":
|
||||
return handleGetFilesUserInfo(request, fileRequest);
|
||||
case "file_by_session":
|
||||
return handleGetFileBySession(request, fileRequest);
|
||||
case "get_user_files":
|
||||
case "user_files":
|
||||
return handleGetUserFiles(request, fileRequest);
|
||||
case "search_files":
|
||||
return handleSearchFiles(request, fileRequest);
|
||||
case "get_storage_usage":
|
||||
case "storage_usage":
|
||||
return handleGetStorageUsage(request, fileRequest);
|
||||
case "delete_file":
|
||||
return handleDeleteFile(request, fileRequest);
|
||||
@@ -47,8 +61,8 @@ public class FileEntityHandler implements RequestHandler {
|
||||
ActionResponse response = ActionResponse.builder()
|
||||
.action(action)
|
||||
.availableActions(Arrays.asList(
|
||||
"get_file_info", "get_file_by_session", "get_user_files",
|
||||
"search_files", "get_storage_usage", "delete_file"))
|
||||
"file_info", "file_by_session", "user_files",
|
||||
"search_files", "storage_usage", "delete_file"))
|
||||
.build();
|
||||
|
||||
return new BaseResponse(request.getMsgId(),
|
||||
@@ -65,6 +79,16 @@ public class FileEntityHandler implements RequestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private BaseResponse handleGetFilesUserInfo(BaseRequest request, FileEntityRequest fileRequest) {
|
||||
Long userId = getUserIdFromToken(fileRequest.getToken());
|
||||
FileInfoUserResponse userFileStats = fileStatsService.getUserFileStats(userId);
|
||||
|
||||
return new BaseResponse(request.getMsgId(),
|
||||
MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(),
|
||||
userFileStats);
|
||||
}
|
||||
|
||||
private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) {
|
||||
try {
|
||||
Long userId = getUserIdFromToken(fileRequest.getToken());
|
||||
@@ -168,17 +192,22 @@ public class FileEntityHandler implements RequestHandler {
|
||||
FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000, request.getVersion());
|
||||
|
||||
var filteredFiles = allFiles.getFiles().stream()
|
||||
.filter(f -> f.getOriginalFileName().toLowerCase().contains(fileRequest.getQuery().toLowerCase()))
|
||||
.filter(f -> (f.getOriginalFileName() + f.getFileExtension()).toLowerCase()
|
||||
.contains(fileRequest.getQuery().toLowerCase()))
|
||||
.toList();
|
||||
|
||||
int start = (page - 1) * pageSize;
|
||||
int end = Math.min(start + pageSize, filteredFiles.size());
|
||||
List<FileEntityResponse> fileEntityResponses = filteredFiles.subList(start, Math.min(end, filteredFiles.size()));
|
||||
long sumSize = fileEntityResponses.stream()
|
||||
.mapToLong(FileEntityResponse::getFileSize)
|
||||
.sum();
|
||||
|
||||
FileListResponse response = FileListResponse.builder()
|
||||
.files(filteredFiles.subList(start, Math.min(end, filteredFiles.size())))
|
||||
.files(fileEntityResponses)
|
||||
.totalCount(filteredFiles.size())
|
||||
.totalSize(allFiles.getTotalSize())
|
||||
.formattedTotalSize(allFiles.getFormattedTotalSize())
|
||||
.totalSize(sumSize)
|
||||
.formattedTotalSize(fileEntityService.formatFileSize(sumSize))
|
||||
.page(page)
|
||||
.pageSize(pageSize)
|
||||
.build();
|
||||
@@ -187,18 +216,13 @@ public class FileEntityHandler implements RequestHandler {
|
||||
MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(),
|
||||
response);
|
||||
|
||||
} catch (NotFoundAuthToken e) {
|
||||
return new BaseResponse(request.getMsgId(),
|
||||
MessageCode.INVALID_TOKEN.getCode(),
|
||||
"Authentication required",
|
||||
null);
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(),
|
||||
"Authentication required", null);
|
||||
} catch (Exception e) {
|
||||
log.error("Error searching files", e);
|
||||
return new BaseResponse(request.getMsgId(),
|
||||
MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||
"Failed to search files: " + e.getMessage(),
|
||||
null);
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||
"Failed to search files: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +259,9 @@ public class FileEntityHandler implements RequestHandler {
|
||||
private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) {
|
||||
try {
|
||||
Long userId = getUserIdFromToken(fileRequest.getToken());
|
||||
FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion());
|
||||
String fileId = fileRequest.getFileId();
|
||||
FileEntityResponse fileInfo = fileEntityService.getById(fileId, request.getVersion());
|
||||
DeleteFileResponse response;
|
||||
|
||||
if (!fileInfo.getUserId().equals(userId)) {
|
||||
return new BaseResponse(request.getMsgId(),
|
||||
@@ -244,18 +270,27 @@ public class FileEntityHandler implements RequestHandler {
|
||||
null);
|
||||
}
|
||||
|
||||
fileEntityService.markAsDeleted(fileRequest.getFileId());
|
||||
FileEntity fileEntity = fileEntityRepository.findById(fileId)
|
||||
.orElseThrow(() -> new FileEntityNotFoundException(fileId));
|
||||
|
||||
DeleteFileResponse response = DeleteFileResponse.builder()
|
||||
.fileId(fileRequest.getFileId())
|
||||
.message("File marked as deleted")
|
||||
.build();
|
||||
if (fileEntity.getStatus().equals(FileStatus.DELETED)) {
|
||||
fileEntityService.deleteFromDisk(fileEntity);
|
||||
response = DeleteFileResponse.builder()
|
||||
.fileId(fileRequest.getFileId())
|
||||
.message("File deleted from disk")
|
||||
.build();
|
||||
} else {
|
||||
fileEntityService.markAsDeleted(fileEntity);
|
||||
response = DeleteFileResponse.builder()
|
||||
.fileId(fileRequest.getFileId())
|
||||
.message("File marked as deleted")
|
||||
.build();
|
||||
}
|
||||
|
||||
return new BaseResponse(request.getMsgId(),
|
||||
MessageCode.SUCCESS.getCode(),
|
||||
"File marked as deleted",
|
||||
response);
|
||||
|
||||
} catch (NotFoundAuthToken e) {
|
||||
return new BaseResponse(request.getMsgId(),
|
||||
MessageCode.INVALID_TOKEN.getCode(),
|
||||
|
||||
@@ -3,9 +3,11 @@ package ru.soune.nocopy.handler;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import ru.soune.nocopy.dto.*;
|
||||
import ru.soune.nocopy.dto.file.*;
|
||||
import ru.soune.nocopy.entity.AuthToken;
|
||||
@@ -17,21 +19,26 @@ import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||
import ru.soune.nocopy.repository.FileUploadSessionRepository;
|
||||
import ru.soune.nocopy.service.file.FileUploadService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class FileUploadHandler implements RequestHandler {
|
||||
private final FileUploadService fileUploadService;
|
||||
|
||||
private final AuthTokenRepository authTokenRepository;
|
||||
|
||||
private final FileUploadSessionRepository fileUploadSessionRepository;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final FileUploadRequestValidator fileUploadRequestValidator;
|
||||
|
||||
@Value("${file.storage.max-file-size}")
|
||||
private long maxFileSize;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) {
|
||||
try {
|
||||
@@ -45,18 +52,18 @@ public class FileUploadHandler implements RequestHandler {
|
||||
return handleInitUpload(request, fileRequest);
|
||||
case "file_types":
|
||||
return handleGetFileTypes(request);
|
||||
case "file_extension":
|
||||
return handleGetFileExtensions(fileRequest, request);
|
||||
case "chunks":
|
||||
return handleGetChunkStatus(request, fileRequest);
|
||||
case "cancel":
|
||||
return handleCancelUpload(request, fileRequest);
|
||||
case "progress":
|
||||
return handleGetProgress(request, fileRequest);
|
||||
case "retry":
|
||||
return handleRetryUpload(request, fileRequest);
|
||||
default:
|
||||
ActionResponse response = ActionResponse.builder()
|
||||
.action(action)
|
||||
.availableActions(Arrays.asList("init", "file_types", "chunks", "cancel", "progress", "retry"))
|
||||
.availableActions(Arrays.asList("init", "file_types", "chunks", "cancel", "progress"))
|
||||
.build();
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_ACTION.getCode(),
|
||||
@@ -70,18 +77,31 @@ public class FileUploadHandler implements RequestHandler {
|
||||
}
|
||||
|
||||
private BaseResponse handleInitUpload(BaseRequest request, FileUploadRequest fileRequest) {
|
||||
try {
|
||||
String token = fileRequest.getToken();
|
||||
|
||||
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
|
||||
|
||||
if (tokenOptional.isEmpty()) {
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(),
|
||||
MessageCode.INVALID_TOKEN.getDescription(), Map.of("token", token));
|
||||
}
|
||||
|
||||
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token not found"));
|
||||
|
||||
BindingResult bindingResult = new BeanPropertyBindingResult(fileRequest, "fileRequest");
|
||||
fileUploadRequestValidator.validate(fileRequest, bindingResult);
|
||||
|
||||
if (bindingResult.hasErrors()) {
|
||||
throw new ValidationException(bindingResult, request.getMsgId());
|
||||
Map<String, String> fieldErrors = bindingResult.getFieldErrors()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
FieldError::getField,
|
||||
fieldError -> fieldError.getDefaultMessage() != null
|
||||
? fieldError.getDefaultMessage()
|
||||
: "Validation error"));
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_FIELD.getCode(),
|
||||
MessageCode.INVALID_FIELD.getDescription(), fieldErrors);
|
||||
}
|
||||
|
||||
FileUploadSession session = fileUploadService.initUpload(
|
||||
@@ -101,15 +121,6 @@ public class FileUploadHandler implements RequestHandler {
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), fileResponse);
|
||||
|
||||
} catch (NotFoundAuthToken e) {
|
||||
InitFileResponse initFileResponse = InitFileResponse.builder()
|
||||
.build();
|
||||
|
||||
throw new NotValidFieldException("Invalid or expired token: " + fileRequest.getToken(),
|
||||
new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(),
|
||||
MessageCode.INVALID_TOKEN.getDescription(), initFileResponse));
|
||||
}
|
||||
}
|
||||
|
||||
private BaseResponse handleGetProgress(BaseRequest request, FileUploadRequest fileRequest) {
|
||||
@@ -240,4 +251,24 @@ public class FileUploadHandler implements RequestHandler {
|
||||
"Failed to get file types", null);
|
||||
}
|
||||
}
|
||||
|
||||
private BaseResponse handleGetFileExtensions(FileUploadRequest fileRequest, BaseRequest request) {
|
||||
try {
|
||||
FileType fileType = FileType.valueOf(fileRequest.getFileType().toUpperCase());
|
||||
List<String> allowedExtensions = fileType.getAllowedExtensions();
|
||||
|
||||
FileExtensionResponse response = FileExtensionResponse.builder()
|
||||
.extension(allowedExtensions)
|
||||
.count(allowedExtensions.size())
|
||||
.maxFileSize(maxFileSize)
|
||||
.build();
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), response);
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting file extensions", e);
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||
"Failed to get file extensions", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ import ru.soune.nocopy.dto.file.FileUploadRequest;
|
||||
import ru.soune.nocopy.entity.file.FileType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class FileUploadRequestValidator implements Validator {
|
||||
private final List<FileType> supportedFileTypes = Arrays.asList(FileType.values());
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return FileUploadRequest.class.isAssignableFrom(clazz);
|
||||
@@ -21,7 +24,7 @@ public class FileUploadRequestValidator implements Validator {
|
||||
|
||||
validateType(request.getFileType(), errors);
|
||||
validateFileName(request.getFileName(), errors);
|
||||
validateExtension(request.getExtension(), errors);
|
||||
validateExtension(request.getExtension(), request.getFileType(), errors);
|
||||
}
|
||||
|
||||
private void validateType(String fileType, Errors errors) {
|
||||
@@ -31,7 +34,12 @@ public class FileUploadRequestValidator implements Validator {
|
||||
}
|
||||
|
||||
try {
|
||||
FileType.valueOf(fileType.toUpperCase());
|
||||
FileType parsedType = FileType.valueOf(fileType.toUpperCase());
|
||||
|
||||
if (!supportedFileTypes.contains(parsedType)) {
|
||||
errors.rejectValue("fileType", "fileType.unsupported",
|
||||
"Unsupported file type. Valid types: " + Arrays.toString(FileType.values()));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
errors.rejectValue("fileType", "fileType.invalid",
|
||||
"Invalid file type. Valid types: " + Arrays.toString(FileType.values()));
|
||||
@@ -50,10 +58,34 @@ public class FileUploadRequestValidator implements Validator {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateExtension(String extension, Errors errors) {
|
||||
private void validateExtension(String extension, String fileType, Errors errors) {
|
||||
if (extension == null || extension.isBlank()) {
|
||||
errors.rejectValue("extension", "extension.required", "Extension is required");
|
||||
errors.rejectValue("fileType", "fileType.required", "File type is required");
|
||||
return;
|
||||
}
|
||||
|
||||
if (extension.contains(".")) {
|
||||
errors.rejectValue("extension", "extension.required", "Extension contains comma");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
FileType parsedType = FileType.valueOf(fileType.toUpperCase());
|
||||
|
||||
if (!parsedType.supportsExtension(extension)) {
|
||||
errors.rejectValue("fileName", "fileType.extension.mismatch",
|
||||
String.format("File extension '%s' does not match file type '%s'. Allowed extensions for %s: %s",
|
||||
extension, parsedType.getDisplayName(), parsedType.getDisplayName(),
|
||||
parsedType.getAllowedExtensions()));
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
errors.rejectValue("fileType", "fileType.invalid",
|
||||
"Invalid file type. Valid types: " + Arrays.toString(FileType.values()));
|
||||
}
|
||||
}
|
||||
|
||||
private String getFileExtension(String fileName) {
|
||||
return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user