This commit is contained in:
@@ -8,15 +8,22 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import ru.soune.nocopy.dto.BaseRequest;
|
import ru.soune.nocopy.dto.BaseRequest;
|
||||||
import ru.soune.nocopy.dto.BaseResponse;
|
import ru.soune.nocopy.dto.BaseResponse;
|
||||||
import ru.soune.nocopy.dto.MessageCode;
|
import ru.soune.nocopy.dto.MessageCode;
|
||||||
import ru.soune.nocopy.dto.RegAnswer;
|
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.UploadProgress;
|
||||||
|
import ru.soune.nocopy.entity.file.UploadStatus;
|
||||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||||
import ru.soune.nocopy.exception.ValidationException;
|
import ru.soune.nocopy.exception.ValidationException;
|
||||||
|
import ru.soune.nocopy.handler.FileUploadHandler;
|
||||||
import ru.soune.nocopy.handler.LoginRequestHandler;
|
import ru.soune.nocopy.handler.LoginRequestHandler;
|
||||||
import ru.soune.nocopy.handler.RegRequestHandler;
|
import ru.soune.nocopy.handler.RegRequestHandler;
|
||||||
import ru.soune.nocopy.handler.RequestHandler;
|
import ru.soune.nocopy.handler.RequestHandler;
|
||||||
|
import ru.soune.nocopy.service.file.FileUploadService;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -29,6 +36,7 @@ import java.util.stream.Collectors;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ApiController {
|
public class ApiController {
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
private final FileUploadService fileUploadService;
|
||||||
|
|
||||||
private Map<Integer, RequestHandler> handlers = new HashMap<>();
|
private Map<Integer, RequestHandler> handlers = new HashMap<>();
|
||||||
|
|
||||||
@@ -36,7 +44,9 @@ public class ApiController {
|
|||||||
public void init() {
|
public void init() {
|
||||||
RegRequestHandler regHandler = applicationContext.getBean(RegRequestHandler.class);
|
RegRequestHandler regHandler = applicationContext.getBean(RegRequestHandler.class);
|
||||||
LoginRequestHandler loginHandler = applicationContext.getBean(LoginRequestHandler.class);
|
LoginRequestHandler loginHandler = applicationContext.getBean(LoginRequestHandler.class);
|
||||||
|
FileUploadHandler fileUploadHandler = applicationContext.getBean(FileUploadHandler.class);
|
||||||
|
|
||||||
|
handlers.put(20004, fileUploadHandler);
|
||||||
handlers.put(20002, regHandler);
|
handlers.put(20002, regHandler);
|
||||||
handlers.put(20001, loginHandler);
|
handlers.put(20001, loginHandler);
|
||||||
}
|
}
|
||||||
@@ -76,6 +86,155 @@ public class ApiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/v{version}/files/chunk")
|
||||||
|
public ResponseEntity<BaseResponse> uploadChunk(
|
||||||
|
@PathVariable("version") int version,
|
||||||
|
@RequestParam("upload_id") String uploadId,
|
||||||
|
@RequestParam("chunk_number") Integer chunkNumber,
|
||||||
|
@RequestParam("chunk") MultipartFile chunk) {
|
||||||
|
|
||||||
|
log.info("Uploading chunk {} for session {}, file size: {} bytes, version: {}",
|
||||||
|
chunkNumber, uploadId, chunk.getSize(), version);
|
||||||
|
|
||||||
|
if (chunk.isEmpty()) {
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Chunk file is empty", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadId == null || uploadId.isBlank()) {
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Upload ID is required", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkNumber == null || chunkNumber < 0) {
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Valid chunk number is required", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileUploadService.uploadChunk(uploadId, chunkNumber, chunk);
|
||||||
|
|
||||||
|
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
|
||||||
|
.uploadId(uploadId)
|
||||||
|
.chunkNumber(chunkNumber)
|
||||||
|
.chunkSize(chunk.getSize())
|
||||||
|
.message("Chunk uploaded successfully")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(),
|
||||||
|
"Chunk uploaded successfully", responseBody));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error uploading chunk", e);
|
||||||
|
|
||||||
|
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
|
||||||
|
.uploadId(uploadId)
|
||||||
|
.chunkNumber(chunkNumber)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(
|
||||||
|
20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Failed to upload chunk: " + e.getMessage(), responseBody));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/v{version}/files/progress/{uploadId}")
|
||||||
|
public ResponseEntity<BaseResponse> getUploadProgress(
|
||||||
|
@PathVariable("version") int version,
|
||||||
|
@PathVariable String uploadId) {
|
||||||
|
|
||||||
|
log.info("Getting progress for upload session: {}, version: {}", uploadId, version);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var progress = fileUploadService.getUploadProgress(uploadId);
|
||||||
|
|
||||||
|
UploadProgress responseBody = UploadProgress.builder()
|
||||||
|
.uploadId(progress.getUploadId())
|
||||||
|
.fileName(progress.getFileName())
|
||||||
|
.totalChunks(progress.getTotalChunks())
|
||||||
|
.uploadedChunks(progress.getUploadedChunks())
|
||||||
|
.status(progress.getStatus().toString())
|
||||||
|
.progressPercentage(progress.getProgressPercentage())
|
||||||
|
.filePath(progress.getFilePath())
|
||||||
|
.remainingChunks(progress.getTotalChunks() - progress.getUploadedChunks())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(
|
||||||
|
20004, MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), responseBody));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error getting progress for upload: {}", uploadId, e);
|
||||||
|
|
||||||
|
UploadProgress responseBody = UploadProgress.builder()
|
||||||
|
.uploadId(uploadId)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(
|
||||||
|
20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Failed to get upload progress: " + e.getMessage(), responseBody));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/v{version}/files/{uploadId}/complete")
|
||||||
|
public ResponseEntity<BaseResponse> completeUpload(
|
||||||
|
@PathVariable("version") int version,
|
||||||
|
@PathVariable String uploadId) {
|
||||||
|
|
||||||
|
log.info("Completing upload session: {}, version: {}", uploadId, version);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var progress = fileUploadService.getUploadProgress(uploadId);
|
||||||
|
|
||||||
|
if (progress.getStatus() == UploadStatus.COMPLETED) {
|
||||||
|
CompleteUploadResponse responseBody = CompleteUploadResponse.builder()
|
||||||
|
.uploadId(progress.getUploadId())
|
||||||
|
.status(progress.getStatus().toString())
|
||||||
|
.uploadedChunks(progress.getUploadedChunks())
|
||||||
|
.totalChunks(progress.getTotalChunks())
|
||||||
|
.message("Upload already completed")
|
||||||
|
.filePath(progress.getFilePath())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(),
|
||||||
|
"Upload already completed", responseBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress.getUploadedChunks() < progress.getTotalChunks()) {
|
||||||
|
CompleteUploadResponse responseBody = CompleteUploadResponse.builder()
|
||||||
|
.uploadId(progress.getUploadId())
|
||||||
|
.status(progress.getStatus().toString())
|
||||||
|
.uploadedChunks(progress.getUploadedChunks())
|
||||||
|
.totalChunks(progress.getTotalChunks())
|
||||||
|
.message("Not all chunks uploaded")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.INCOMPLETE_UPLOAD.getCode(),
|
||||||
|
"Not all chunks uploaded", responseBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
CompleteUploadResponse responseBody = CompleteUploadResponse.builder()
|
||||||
|
.uploadId(progress.getUploadId())
|
||||||
|
.status(progress.getStatus().toString())
|
||||||
|
.uploadedChunks(progress.getUploadedChunks())
|
||||||
|
.totalChunks(progress.getTotalChunks())
|
||||||
|
.message("File assembly in progress")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(),
|
||||||
|
"File assembly in progress", responseBody));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error completing upload: {}", uploadId, e);
|
||||||
|
|
||||||
|
CompleteUploadResponse responseBody = CompleteUploadResponse.builder()
|
||||||
|
.uploadId(uploadId)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Failed to complete upload: " + e.getMessage(), responseBody));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ResponseEntity<BaseResponse> createValidationErrorResponse(BindingResult bindingResult, Integer msgId) {
|
private ResponseEntity<BaseResponse> createValidationErrorResponse(BindingResult bindingResult, Integer msgId) {
|
||||||
List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors()
|
List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors()
|
||||||
.stream()
|
.stream()
|
||||||
|
|||||||
@@ -31,38 +31,6 @@ public class FileUploadController {
|
|||||||
|
|
||||||
private final FileUploadService fileUploadService;
|
private final FileUploadService fileUploadService;
|
||||||
|
|
||||||
private final AuthTokenRepository authTokenRepository;
|
|
||||||
|
|
||||||
private final FileUploadSessionRepository fileUploadSessionRepository;
|
|
||||||
|
|
||||||
//TODO ADD TEMPLATE JSON, RESPONSE OK,ADD MESSAGE CODE
|
|
||||||
@PostMapping("/init")
|
|
||||||
public ResponseEntity<FileApiResponse<FileUploadSession>> initUpload(@Valid @RequestBody InitUploadRequest request,
|
|
||||||
@RequestHeader("Authorization") String tokenHeader) {
|
|
||||||
String token = tokenHeader.replace("Bearer ", "");
|
|
||||||
|
|
||||||
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
|
|
||||||
|
|
||||||
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
|
|
||||||
|
|
||||||
try {
|
|
||||||
FileUploadSession session = fileUploadService.initUpload(authToken.getUser().getId(), request.getFileName(),
|
|
||||||
request.getFileType(), request.getExtension(), request.getFileSize());
|
|
||||||
|
|
||||||
return ResponseEntity.ok(FileApiResponse.success(session));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error initializing upload", e);
|
|
||||||
return ResponseEntity.badRequest()
|
|
||||||
.body(FileApiResponse.error(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/file-types")
|
|
||||||
public ResponseEntity<List<FileType>> getFileTypes() {
|
|
||||||
return ResponseEntity.ok(Arrays.asList(FileType.values()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/chunk")
|
@PostMapping("/chunk")
|
||||||
public ResponseEntity<FileApiResponse<UploadProgressResponse>> uploadChunk(
|
public ResponseEntity<FileApiResponse<UploadProgressResponse>> uploadChunk(
|
||||||
@RequestParam("uploadId") String uploadId,
|
@RequestParam("uploadId") String uploadId,
|
||||||
@@ -94,131 +62,4 @@ public class FileUploadController {
|
|||||||
.body(FileApiResponse.error(e.getMessage()));
|
.body(FileApiResponse.error(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/progress/{uploadId}")
|
|
||||||
public ResponseEntity<FileApiResponse<UploadProgressResponse>> getProgress(
|
|
||||||
@PathVariable String uploadId) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId);
|
|
||||||
return ResponseEntity.ok(FileApiResponse.success(progress));
|
|
||||||
|
|
||||||
} catch (UploadSessionNotFoundException e) {
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
||||||
.body(FileApiResponse.error("Upload session not found"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error getting progress", e);
|
|
||||||
return ResponseEntity.badRequest()
|
|
||||||
.body(FileApiResponse.error("Failed to get progress"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{uploadId}/complete")
|
|
||||||
public ResponseEntity<FileApiResponse<UploadProgressResponse>> completeUpload(
|
|
||||||
@PathVariable String uploadId) {
|
|
||||||
|
|
||||||
log.info("Manual completion requested for session: {}", uploadId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId);
|
|
||||||
|
|
||||||
if (progress.getStatus() == UploadStatus.COMPLETED) {
|
|
||||||
return ResponseEntity.ok(FileApiResponse.success(
|
|
||||||
"Upload already completed", progress));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress.getUploadedChunks() < progress.getTotalChunks()) {
|
|
||||||
return ResponseEntity.badRequest()
|
|
||||||
.body(FileApiResponse.error(
|
|
||||||
String.format("Not all chunks uploaded: %d/%d",
|
|
||||||
progress.getUploadedChunks(), progress.getTotalChunks())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok(FileApiResponse.success(
|
|
||||||
"File assembly in progress", progress));
|
|
||||||
|
|
||||||
} catch (UploadSessionNotFoundException e) {
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
||||||
.body(FileApiResponse.error("Upload session not found"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error completing upload", e);
|
|
||||||
return ResponseEntity.internalServerError()
|
|
||||||
.body(FileApiResponse.error("Failed to complete upload"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{uploadId}/cancel")
|
|
||||||
public ResponseEntity<FileApiResponse<Void>> cancelUpload(
|
|
||||||
@PathVariable String uploadId) {
|
|
||||||
|
|
||||||
log.info("Cancelling upload session: {}", uploadId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fileUploadService.cancelUpload(uploadId);
|
|
||||||
return ResponseEntity.ok(FileApiResponse.success(
|
|
||||||
"Upload cancelled successfully", null));
|
|
||||||
|
|
||||||
} catch (UploadSessionNotFoundException e) {
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
||||||
.body(FileApiResponse.error("Upload session not found"));
|
|
||||||
} catch (FileUploadException e) {
|
|
||||||
return ResponseEntity.badRequest()
|
|
||||||
.body(FileApiResponse.error(e.getMessage()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error cancelling upload", e);
|
|
||||||
return ResponseEntity.internalServerError()
|
|
||||||
.body(FileApiResponse.error("Failed to cancel upload"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{uploadId}/retry")
|
|
||||||
public ResponseEntity<FileApiResponse<UploadProgressResponse>> retryUpload(
|
|
||||||
@PathVariable String uploadId) {
|
|
||||||
|
|
||||||
log.info("Retry requested for failed upload: {}", uploadId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fileUploadService.retryFailedUpload(uploadId);
|
|
||||||
UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId);
|
|
||||||
|
|
||||||
return ResponseEntity.ok(FileApiResponse.success(
|
|
||||||
"Upload retry initiated", progress));
|
|
||||||
|
|
||||||
} catch (UploadSessionNotFoundException e) {
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
||||||
.body(FileApiResponse.error("Upload session not found"));
|
|
||||||
} catch (FileUploadException e) {
|
|
||||||
return ResponseEntity.badRequest()
|
|
||||||
.body(FileApiResponse.error(e.getMessage()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error retrying upload", e);
|
|
||||||
return ResponseEntity.internalServerError()
|
|
||||||
.body(FileApiResponse.error("Failed to retry upload"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{uploadId}/chunks")
|
|
||||||
public ResponseEntity<FileApiResponse<Map<Integer, Boolean>>> getChunkStatus(
|
|
||||||
@PathVariable String uploadId) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
FileUploadSession session = fileUploadSessionRepository.findById(uploadId)
|
|
||||||
.orElseThrow(() -> new UploadSessionNotFoundException(uploadId));
|
|
||||||
|
|
||||||
Map<Integer, Boolean> chunkStatus = new HashMap<>();
|
|
||||||
for (int i = 0; i < session.getTotalChunks(); i++) {
|
|
||||||
chunkStatus.put(i, session.getChunkPaths().containsKey(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok(FileApiResponse.success(chunkStatus));
|
|
||||||
|
|
||||||
} catch (UploadSessionNotFoundException e) {
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
||||||
.body(FileApiResponse.error("Upload session not found"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error getting chunk status", e);
|
|
||||||
return ResponseEntity.internalServerError()
|
|
||||||
.body(FileApiResponse.error("Failed to get chunk status"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ public enum MessageCode {
|
|||||||
REG_EMAIL_EXISTS(1, "Email already registered"),
|
REG_EMAIL_EXISTS(1, "Email already registered"),
|
||||||
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"),
|
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"),
|
||||||
INVALID_FIELD(2, "Invalid field"),
|
INVALID_FIELD(2, "Invalid field"),
|
||||||
|
INVALID_TOKEN(2, "Invalid token"),
|
||||||
|
INVALID_ACTION(2, "Invalid action"),
|
||||||
|
FILE_UPLOAD_ERROR(2, "File upload error"),
|
||||||
INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
|
INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
|
||||||
|
INCOMPLETE_UPLOAD(2, "Not load all chunks"),
|
||||||
MSG_ID_NOT_FOUND(4, "Message id not found"),
|
MSG_ID_NOT_FOUND(4, "Message id not found"),
|
||||||
|
|
||||||
AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
|
AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
|
||||||
|
FILE_NOT_FOUND(4, "File not found"),
|
||||||
AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match");
|
AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match");
|
||||||
|
|
||||||
private final Integer code;
|
private final Integer code;
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ActionResponse {
|
||||||
|
private List<String> availableActions;
|
||||||
|
private String action;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class CanceledUploadResponse {
|
||||||
|
@JsonProperty("upload_id")
|
||||||
|
private String uploadId;
|
||||||
|
|
||||||
|
@JsonProperty("message")
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ChunkStatusResponse {
|
||||||
|
@JsonProperty("upload_id")
|
||||||
|
private String uploadId;
|
||||||
|
|
||||||
|
@JsonProperty("total_chunks")
|
||||||
|
private Integer totalChunks;
|
||||||
|
|
||||||
|
@JsonProperty("chunk_status")
|
||||||
|
private Map<String, Boolean> chunkStatus;
|
||||||
|
|
||||||
|
@JsonProperty("uploaded_chunks")
|
||||||
|
private Integer uploadedChunks;
|
||||||
|
|
||||||
|
@JsonProperty("missing_chunks")
|
||||||
|
private Integer missingChunks;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChunkUploadResponse {
|
||||||
|
@JsonProperty("upload_id")
|
||||||
|
private String uploadId;
|
||||||
|
|
||||||
|
@JsonProperty("chunk_number")
|
||||||
|
private Integer chunkNumber;
|
||||||
|
|
||||||
|
@JsonProperty("chunk_size")
|
||||||
|
private Long chunkSize;
|
||||||
|
|
||||||
|
@JsonProperty("message")
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CompleteUploadResponse {
|
||||||
|
@JsonProperty("upload_id")
|
||||||
|
private String uploadId;
|
||||||
|
|
||||||
|
@JsonProperty("status")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@JsonProperty("uploaded_chunks")
|
||||||
|
private Integer uploadedChunks;
|
||||||
|
|
||||||
|
@JsonProperty("total_chunks")
|
||||||
|
private Integer totalChunks;
|
||||||
|
|
||||||
|
@JsonProperty("message")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@JsonProperty("file_path")
|
||||||
|
private String filePath;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import ru.soune.nocopy.entity.file.FileType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FileTypesResponse {
|
||||||
|
@JsonProperty("file_types")
|
||||||
|
private List<FileType> fileTypes;
|
||||||
|
|
||||||
|
@JsonProperty("count")
|
||||||
|
private Integer count;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class FileUploadRequest {
|
||||||
|
@JsonProperty("upload_id")
|
||||||
|
private String uploadId;
|
||||||
|
|
||||||
|
@JsonProperty("file_name")
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@JsonProperty("file_type")
|
||||||
|
private String fileType;
|
||||||
|
|
||||||
|
@JsonProperty("extension")
|
||||||
|
private String extension;
|
||||||
|
|
||||||
|
@JsonProperty("file_size")
|
||||||
|
private Long fileSize;
|
||||||
|
|
||||||
|
@JsonProperty("chunk_number")
|
||||||
|
private Integer chunkNumber;
|
||||||
|
|
||||||
|
@JsonProperty("action")
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
@JsonProperty("token")
|
||||||
|
private String token;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class InitFileResponse {
|
||||||
|
@JsonProperty("upload_id")
|
||||||
|
private String uploadId;
|
||||||
|
|
||||||
|
@JsonProperty("file_name")
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@JsonProperty("total_chunks")
|
||||||
|
private Integer totalChunks;
|
||||||
|
|
||||||
|
@JsonProperty("chunk_size")
|
||||||
|
private Long chunkSize;
|
||||||
|
|
||||||
|
@JsonProperty("status")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@JsonProperty("field_errors")
|
||||||
|
private List<Map<String, String>> fieldErrors;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class RetryUploadResponse {
|
||||||
|
@JsonProperty("upload_id")
|
||||||
|
private String uploadId;
|
||||||
|
|
||||||
|
@JsonProperty("message")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@JsonProperty("status")
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package ru.soune.nocopy.dto.file;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UploadProgress {
|
||||||
|
@JsonProperty("upload_id")
|
||||||
|
private String uploadId;
|
||||||
|
|
||||||
|
@JsonProperty("file_name")
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@JsonProperty("total_chunks")
|
||||||
|
private Integer totalChunks;
|
||||||
|
|
||||||
|
@JsonProperty("uploaded_chunks")
|
||||||
|
private Integer uploadedChunks;
|
||||||
|
|
||||||
|
@JsonProperty("status")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@JsonProperty("progress_percentage")
|
||||||
|
private Integer progressPercentage;
|
||||||
|
|
||||||
|
@JsonProperty("file_path")
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
|
@JsonProperty("remaining_chunks")
|
||||||
|
private Integer remainingChunks;
|
||||||
|
|
||||||
|
@JsonProperty("message")
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@@ -33,6 +33,9 @@ public class FileUploadSession {
|
|||||||
@Column(name = "file_size")
|
@Column(name = "file_size")
|
||||||
private Long fileSize;
|
private Long fileSize;
|
||||||
|
|
||||||
|
@Column(name = "chunk_size")
|
||||||
|
private Long chunkSize;
|
||||||
|
|
||||||
@Column(name = "total_chunks")
|
@Column(name = "total_chunks")
|
||||||
private Integer totalChunks;
|
private Integer totalChunks;
|
||||||
|
|
||||||
@@ -84,9 +87,14 @@ public class FileUploadSession {
|
|||||||
public void prePersist() {
|
public void prePersist() {
|
||||||
this.createdAt = LocalDateTime.now();
|
this.createdAt = LocalDateTime.now();
|
||||||
this.expiresAt = LocalDateTime.now().plusHours(24);
|
this.expiresAt = LocalDateTime.now().plusHours(24);
|
||||||
this.chunksUploaded = 0;
|
if (this.chunksUploaded == null) {
|
||||||
|
this.chunksUploaded = 0;
|
||||||
|
}
|
||||||
if (this.status == null) {
|
if (this.status == null) {
|
||||||
this.status = UploadStatus.INITIATED;
|
this.status = UploadStatus.INITIATED;
|
||||||
}
|
}
|
||||||
|
if (this.retryCount == null) {
|
||||||
|
this.retryCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package ru.soune.nocopy.handler;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.validation.BeanPropertyBindingResult;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import ru.soune.nocopy.dto.*;
|
||||||
|
import ru.soune.nocopy.dto.file.*;
|
||||||
|
import ru.soune.nocopy.entity.AuthToken;
|
||||||
|
import ru.soune.nocopy.entity.file.FileType;
|
||||||
|
import ru.soune.nocopy.entity.file.FileUploadSession;
|
||||||
|
import ru.soune.nocopy.exception.*;
|
||||||
|
import ru.soune.nocopy.handler.validator.FileUploadRequestValidator;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseResponse handle(BaseRequest request) {
|
||||||
|
try {
|
||||||
|
FileUploadRequest fileRequest = objectMapper.convertValue(
|
||||||
|
request.getMessageBody(), FileUploadRequest.class);
|
||||||
|
|
||||||
|
String action = fileRequest.getAction();
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case "init":
|
||||||
|
return handleInitUpload(request, fileRequest);
|
||||||
|
case "file_types":
|
||||||
|
return handleGetFileTypes(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"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_ACTION.getCode(),
|
||||||
|
"Invalid action: " + action, response);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error in FileUploadHandler", e);
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Handler error: " + e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleInitUpload(BaseRequest request, FileUploadRequest fileRequest) {
|
||||||
|
try {
|
||||||
|
String token = fileRequest.getToken();
|
||||||
|
|
||||||
|
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(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());
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUploadSession session = fileUploadService.initUpload(
|
||||||
|
authToken.getUser().getId(),
|
||||||
|
fileRequest.getFileName(),
|
||||||
|
fileRequest.getFileType(),
|
||||||
|
fileRequest.getExtension(),
|
||||||
|
fileRequest.getFileSize());
|
||||||
|
|
||||||
|
InitFileResponse fileResponse = InitFileResponse.builder()
|
||||||
|
.uploadId(session.getUploadId())
|
||||||
|
.fileName(session.getFileName())
|
||||||
|
.totalChunks(session.getTotalChunks())
|
||||||
|
.chunkSize(session.getChunkSize())
|
||||||
|
.status(session.getStatus().toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
ru.soune.nocopy.dto.file.UploadProgressResponse progress =
|
||||||
|
fileUploadService.getUploadProgress(fileRequest.getUploadId());
|
||||||
|
|
||||||
|
UploadProgressResponse responseDto = UploadProgressResponse.builder()
|
||||||
|
.uploadId(progress.getUploadId())
|
||||||
|
.fileName(progress.getFileName())
|
||||||
|
.totalChunks(progress.getTotalChunks())
|
||||||
|
.uploadedChunks(progress.getUploadedChunks())
|
||||||
|
.status(progress.getStatus())
|
||||||
|
.progressPercentage(progress.getProgressPercentage())
|
||||||
|
.filePath(progress.getFilePath())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||||
|
MessageCode.SUCCESS.getDescription(), responseDto);
|
||||||
|
|
||||||
|
} catch (UploadSessionNotFoundException e) {
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(),
|
||||||
|
MessageCode.FILE_NOT_FOUND.getDescription(), null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error getting progress", e);
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
MessageCode.FILE_UPLOAD_ERROR.getDescription(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleCancelUpload(BaseRequest request, FileUploadRequest fileRequest) {
|
||||||
|
try {
|
||||||
|
fileUploadService.cancelUpload(fileRequest.getUploadId());
|
||||||
|
|
||||||
|
CanceledUploadResponse response = CanceledUploadResponse.builder()
|
||||||
|
.uploadId(fileRequest.getUploadId())
|
||||||
|
.message("Upload cancelled successfully")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||||
|
"Upload cancelled successfully", response);
|
||||||
|
|
||||||
|
} catch (UploadSessionNotFoundException e) {
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(),
|
||||||
|
"Upload session not found", null);
|
||||||
|
} catch (FileUploadException e) {
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), e.getMessage(), null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error cancelling upload", e);
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Failed to cancel upload", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleRetryUpload(BaseRequest request, FileUploadRequest fileRequest) {
|
||||||
|
try {
|
||||||
|
fileUploadService.retryFailedUpload(fileRequest.getUploadId());
|
||||||
|
ru.soune.nocopy.dto.file.UploadProgressResponse progress =
|
||||||
|
fileUploadService.getUploadProgress(fileRequest.getUploadId());
|
||||||
|
|
||||||
|
RetryUploadResponse response = RetryUploadResponse.builder()
|
||||||
|
.uploadId(progress.getUploadId())
|
||||||
|
.message("Upload retry initiated")
|
||||||
|
.status(progress.getStatus().toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), "Upload retry initiated",
|
||||||
|
response);
|
||||||
|
|
||||||
|
} catch (UploadSessionNotFoundException e) {
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(),
|
||||||
|
"Upload session not found", null);
|
||||||
|
} catch (FileUploadException e) {
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), e.getMessage(),
|
||||||
|
null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error retrying upload", e);
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Failed to retry upload", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleGetChunkStatus(BaseRequest request, FileUploadRequest fileRequest) {
|
||||||
|
try {
|
||||||
|
FileUploadSession session = fileUploadSessionRepository.findById(fileRequest.getUploadId())
|
||||||
|
.orElseThrow(() -> new UploadSessionNotFoundException(fileRequest.getUploadId()));
|
||||||
|
|
||||||
|
Map<String, Boolean> chunkStatus = new HashMap<>();
|
||||||
|
for (int i = 0; i < session.getTotalChunks(); i++) {
|
||||||
|
chunkStatus.put("chunk_" + i, session.getChunkPaths().containsKey(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkStatusResponse response = ChunkStatusResponse.builder()
|
||||||
|
.uploadId(fileRequest.getUploadId())
|
||||||
|
.totalChunks(session.getTotalChunks())
|
||||||
|
.uploadedChunks(session.getChunksUploaded())
|
||||||
|
.missingChunks(session.getTotalChunks() - session.getChunksUploaded())
|
||||||
|
.chunkStatus(chunkStatus)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||||
|
MessageCode.SUCCESS.getDescription(), response);
|
||||||
|
|
||||||
|
} catch (UploadSessionNotFoundException e) {
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(),
|
||||||
|
"Upload session not found", null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error getting chunk status", e);
|
||||||
|
return new BaseResponse(request.getMsgId(),
|
||||||
|
MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to get chunk status",
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseResponse handleGetFileTypes(BaseRequest request) {
|
||||||
|
try {
|
||||||
|
FileTypesResponse response = FileTypesResponse.builder()
|
||||||
|
.fileTypes(Arrays.asList(FileType.values()))
|
||||||
|
.count(FileType.values().length)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||||
|
MessageCode.SUCCESS.getDescription(), response);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error getting file types", e);
|
||||||
|
return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(),
|
||||||
|
"Failed to get file types", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package ru.soune.nocopy.handler.validator;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.validation.Validator;
|
||||||
|
import ru.soune.nocopy.dto.file.FileUploadRequest;
|
||||||
|
import ru.soune.nocopy.entity.file.FileType;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class FileUploadRequestValidator implements Validator {
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return FileUploadRequest.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(Object target, Errors errors) {
|
||||||
|
FileUploadRequest request = (FileUploadRequest) target;
|
||||||
|
|
||||||
|
validateType(request.getFileType(), errors);
|
||||||
|
validateFileName(request.getFileName(), errors);
|
||||||
|
validateExtension(request.getExtension(), errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateType(String fileType, Errors errors) {
|
||||||
|
if (fileType == null || fileType.isBlank()) {
|
||||||
|
errors.rejectValue("fileType", "fileType.required", "File type is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileType.valueOf(fileType.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
errors.rejectValue("fileType", "fileType.invalid",
|
||||||
|
"Invalid file type. Valid types: " + Arrays.toString(FileType.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateFileName(String fileName, Errors errors) {
|
||||||
|
if (fileName == null || fileName.isBlank()) {
|
||||||
|
errors.rejectValue("fileName", "fileName.required", "File name is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName.length() > 255) {
|
||||||
|
errors.rejectValue("fileName", "fileName.too.long",
|
||||||
|
"File name too long, max 255 characters");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateExtension(String extension, Errors errors) {
|
||||||
|
if (extension == null || extension.isBlank()) {
|
||||||
|
errors.rejectValue("extension", "extension.required", "Extension is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extension.startsWith(".")) {
|
||||||
|
errors.rejectValue("extension", "extension.invalid.format",
|
||||||
|
"Extension must start with '.'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -510,8 +510,8 @@ public class FileUploadServiceImpl implements FileUploadService {
|
|||||||
FileUploadSession session = sessionRepository.findById(uploadId)
|
FileUploadSession session = sessionRepository.findById(uploadId)
|
||||||
.orElseThrow(() -> new UploadSessionNotFoundException(uploadId));
|
.orElseThrow(() -> new UploadSessionNotFoundException(uploadId));
|
||||||
|
|
||||||
if (session.getStatus() == UploadStatus.COMPLETED) {
|
if (session.getStatus() == UploadStatus.COMPLETED || session.getStatus() == UploadStatus.CANCELLED) {
|
||||||
throw new FileUploadException("Cannot cancel completed upload");
|
throw new FileUploadException("Cannot cancel completed or cancelled upload");
|
||||||
}
|
}
|
||||||
|
|
||||||
session.setStatus(UploadStatus.CANCELLED);
|
session.setStatus(UploadStatus.CANCELLED);
|
||||||
@@ -539,6 +539,7 @@ public class FileUploadServiceImpl implements FileUploadService {
|
|||||||
session.setStatus(UploadStatus.UPLOADING);
|
session.setStatus(UploadStatus.UPLOADING);
|
||||||
session.setRetryCount(session.getRetryCount() + 1);
|
session.setRetryCount(session.getRetryCount() + 1);
|
||||||
session.setLastError(null);
|
session.setLastError(null);
|
||||||
|
|
||||||
sessionRepository.save(session);
|
sessionRepository.save(session);
|
||||||
|
|
||||||
if (session.getChunksUploaded().equals(session.getTotalChunks())) {
|
if (session.getChunksUploaded().equals(session.getTotalChunks())) {
|
||||||
|
|||||||
Reference in New Issue
Block a user