add bone for file system #9

Merged
backdev merged 16 commits from NCBACK-15 into dev 2025-12-21 17:19:35 +08:00
15 changed files with 447 additions and 345 deletions
Showing only changes of commit f3aa6324c2 - Show all commits
@@ -0,0 +1,28 @@
package ru.soune.nocopy.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.soune.nocopy.handler.*;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class HandlerConfig {
@Bean
public Map<Integer, RequestHandler> handlers(
RegRequestHandler reg,
LoginRequestHandler login,
FileUploadHandler upload,
FileEntityHandler file
) {
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login);
map.put(20002, reg);
map.put(20004, upload);
map.put(20005, file);
return map;
}
}
@@ -1,9 +1,7 @@
package ru.soune.nocopy.controller; package ru.soune.nocopy.controller;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.http.ResponseEntity; 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;
@@ -19,10 +17,7 @@ import ru.soune.nocopy.dto.file.UploadProgress;
import ru.soune.nocopy.entity.file.UploadStatus; 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.*;
import ru.soune.nocopy.handler.LoginRequestHandler;
import ru.soune.nocopy.handler.RegRequestHandler;
import ru.soune.nocopy.handler.RequestHandler;
import ru.soune.nocopy.service.file.FileUploadService; import ru.soune.nocopy.service.file.FileUploadService;
import java.util.HashMap; import java.util.HashMap;
@@ -35,21 +30,10 @@ import java.util.stream.Collectors;
@RequestMapping("/api") @RequestMapping("/api")
@RequiredArgsConstructor @RequiredArgsConstructor
public class ApiController { public class ApiController {
private final ApplicationContext applicationContext;
private final FileUploadService fileUploadService; private final FileUploadService fileUploadService;
private Map<Integer, RequestHandler> handlers = new HashMap<>(); private final Map<Integer, RequestHandler> handlers;
@PostConstruct
public void init() {
RegRequestHandler regHandler = applicationContext.getBean(RegRequestHandler.class);
LoginRequestHandler loginHandler = applicationContext.getBean(LoginRequestHandler.class);
FileUploadHandler fileUploadHandler = applicationContext.getBean(FileUploadHandler.class);
handlers.put(20004, fileUploadHandler);
handlers.put(20002, regHandler);
handlers.put(20001, loginHandler);
}
@PostMapping("/v{version}/data") @PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request, public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@@ -1,210 +0,0 @@
package ru.soune.nocopy.controller.file;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.file.FileApiResponse;
import ru.soune.nocopy.dto.file.FileEntityResponse;
import ru.soune.nocopy.dto.file.FileListResponse;
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;
@Slf4j
@RestController
@RequestMapping("/api/file-entities")
@RequiredArgsConstructor
public class FileEntityController {
private final FileEntityService fileEntityService;
private final AuthTokenRepository authTokenRepository;
/**
* Получить информацию о файле по ID
*/
@GetMapping("/{fileId}")
public ResponseEntity<FileApiResponse<FileEntityResponse>> 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)
.body(FileApiResponse.error("Access denied"));
}
return ResponseEntity.ok(FileApiResponse.success(fileInfo));
} catch (NotFoundAuthToken e) {
return ResponseEntity.status(401)
.body(FileApiResponse.error("Authentication required"));
} catch (Exception e) {
log.error("Error getting file info", e);
return ResponseEntity.badRequest()
.body(FileApiResponse.error("Failed to get file info: " + e.getMessage()));
}
}
/**
* Получить информацию о файле по ID сессии загрузки
*/
@GetMapping("/by-session/{uploadSessionId}")
public ResponseEntity<FileApiResponse<FileEntityResponse>> getFileByUploadSession(
@PathVariable String uploadSessionId,
@RequestHeader("Authorization") String tokenHeader) {
try {
Long userId = getUserIdFromToken(tokenHeader);
FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(uploadSessionId);
if (!fileInfo.getUserId().equals(userId)) {
return ResponseEntity.status(403)
.body(FileApiResponse.error("Access denied"));
}
return ResponseEntity.ok(FileApiResponse.success(fileInfo));
} catch (NotFoundAuthToken e) {
return ResponseEntity.status(401)
.body(FileApiResponse.error("Authentication required"));
} catch (Exception e) {
log.error("Error getting file by session", e);
return ResponseEntity.badRequest()
.body(FileApiResponse.error("Failed to get file: " + e.getMessage()));
}
}
/**
* Получить все файлы пользователя
*/
@GetMapping("/my-files")
public ResponseEntity<FileApiResponse<FileListResponse>> getUserFiles(
@RequestHeader("Authorization") String tokenHeader,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int pageSize) {
try {
Long userId = getUserIdFromToken(tokenHeader);
FileListResponse files = fileEntityService.getUserFiles(userId, page, pageSize);
return ResponseEntity.ok(FileApiResponse.success(files));
} catch (NotFoundAuthToken e) {
return ResponseEntity.status(401)
.body(FileApiResponse.error("Authentication required"));
} catch (Exception e) {
log.error("Error getting user files", e);
return ResponseEntity.badRequest()
.body(FileApiResponse.error("Failed to get files: " + e.getMessage()));
}
}
/**
* Поиск файлов пользователя
*/
@GetMapping("/search")
public ResponseEntity<FileApiResponse<FileListResponse>> searchFiles(
@RequestParam String query,
@RequestHeader("Authorization") String tokenHeader,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int pageSize) {
try {
Long userId = getUserIdFromToken(tokenHeader);
FileListResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000);
var filteredFiles = allFiles.getFiles().stream()
.filter(f -> f.getOriginalFileName().toLowerCase().contains(query.toLowerCase()))
.toList();
int start = (page - 1) * pageSize;
int end = Math.min(start + pageSize, filteredFiles.size());
FileListResponse response = FileListResponse.builder()
.files(filteredFiles.subList(start, Math.min(end, filteredFiles.size())))
.totalCount(filteredFiles.size())
.totalSize(allFiles.getTotalSize())
.formattedTotalSize(allFiles.getFormattedTotalSize())
.page(page)
.pageSize(pageSize)
.build();
return ResponseEntity.ok(FileApiResponse.success(response));
} catch (NotFoundAuthToken e) {
return ResponseEntity.status(401)
.body(FileApiResponse.error("Authentication required"));
} catch (Exception e) {
log.error("Error searching files", e);
return ResponseEntity.badRequest()
.body(FileApiResponse.error("Failed to search files: " + e.getMessage()));
}
}
/**
* Получить общий размер файлов пользователя
*/
@GetMapping("/storage/usage")
public ResponseEntity<FileApiResponse<Long>> getStorageUsage(
@RequestHeader("Authorization") String tokenHeader) {
try {
Long userId = getUserIdFromToken(tokenHeader);
long usage = fileEntityService.getUserStorageUsed(userId);
return ResponseEntity.ok(FileApiResponse.success(usage));
} catch (NotFoundAuthToken e) {
return ResponseEntity.status(401)
.body(FileApiResponse.error("Authentication required"));
} catch (Exception e) {
log.error("Error getting storage usage", e);
return ResponseEntity.badRequest()
.body(FileApiResponse.error("Failed to get storage usage: " + e.getMessage()));
}
}
/**
* Пометить файл как удаленный
*/
@DeleteMapping("/{fileId}")
public ResponseEntity<FileApiResponse<Void>> deleteFile(
@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)
.body(FileApiResponse.error("Access denied"));
}
fileEntityService.markAsDeleted(fileId);
return ResponseEntity.ok(FileApiResponse.success(
"File marked as deleted", null));
} catch (NotFoundAuthToken e) {
return ResponseEntity.status(401)
.body(FileApiResponse.error("Authentication required"));
} catch (Exception e) {
log.error("Error deleting file", e);
return ResponseEntity.badRequest()
.body(FileApiResponse.error("Failed to delete file: " + e.getMessage()));
}
}
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();
}
}
@@ -1,65 +0,0 @@
package ru.soune.nocopy.controller.file;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import ru.soune.nocopy.dto.file.FileApiResponse;
import ru.soune.nocopy.dto.file.InitUploadRequest;
import ru.soune.nocopy.dto.file.UploadProgressResponse;
import ru.soune.nocopy.entity.AuthToken;
import ru.soune.nocopy.entity.file.FileType;
import ru.soune.nocopy.entity.file.FileUploadSession;
import ru.soune.nocopy.entity.file.UploadStatus;
import ru.soune.nocopy.exception.FileUploadException;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.exception.UploadSessionNotFoundException;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.FileUploadSessionRepository;
import ru.soune.nocopy.service.file.FileUploadService;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/api/files")
@RequiredArgsConstructor
public class FileUploadController {
private final FileUploadService fileUploadService;
@PostMapping("/chunk")
public ResponseEntity<FileApiResponse<UploadProgressResponse>> uploadChunk(
@RequestParam("uploadId") String uploadId,
@RequestParam("chunkNumber") Integer chunkNumber,
@RequestParam("chunk") MultipartFile chunk) {
log.info("Uploading chunk {} for session {}, file size: {} bytes",
chunkNumber, uploadId, chunk.getSize());
if (chunk.isEmpty()) {
return ResponseEntity.badRequest()
.body(FileApiResponse.error("Chunk file is empty"));
}
if (uploadId == null || uploadId.isBlank()) {
return ResponseEntity.badRequest()
.body(FileApiResponse.error("Upload ID is required"));
}
try {
UploadProgressResponse progress = fileUploadService.uploadChunk(
uploadId, chunkNumber, chunk);
return ResponseEntity.ok(FileApiResponse.success(progress));
} catch (Exception e) {
log.error("Error uploading chunk", e);
return ResponseEntity.badRequest()
.body(FileApiResponse.error(e.getMessage()));
}
}
}
@@ -11,7 +11,8 @@ public enum MessageCode {
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"), 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"),
FILE_ENTITY_ERROR(2, "File entity error"),
ACCESS_DENIED(2, "Access denied"),
AUTH_EMAIL_NOT_FOUND(4, "Email not found"), AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
FILE_NOT_FOUND(4, "File 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");
@@ -0,0 +1,19 @@
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
@AllArgsConstructor
@NoArgsConstructor
public class DeleteFileResponse {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("message")
private String message;
}
@@ -0,0 +1,34 @@
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
@AllArgsConstructor
@NoArgsConstructor
public class FileEntityRequest {
@JsonProperty("action")
private String action;
@JsonProperty("file_id")
private String fileId;
@JsonProperty("upload_session_id")
private String uploadSessionId;
@JsonProperty("query")
private String query;
@JsonProperty("page")
private Integer page;
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("token")
private String token;
}
@@ -1,5 +1,6 @@
package ru.soune.nocopy.dto.file; package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@@ -9,14 +10,24 @@ import java.util.List;
@Data @Data
@Builder @Builder
@NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor
public class FileListResponse { public class FileListResponse {
@JsonProperty("files")
private List<FileEntityResponse> files; private List<FileEntityResponse> files;
private int totalCount;
private long totalSize; @JsonProperty("total_count")
private Integer totalCount;
@JsonProperty("total_size")
private Long totalSize;
@JsonProperty("formatted_total_size")
private String formattedTotalSize; private String formattedTotalSize;
private int page;
private int pageSize; @JsonProperty("page")
private Integer page;
@JsonProperty("page_size")
private Integer pageSize;
} }
@@ -0,0 +1,22 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FileResponse {
private List<FileEntityResponse> files;
private int totalCount;
private long totalSize;
private String formattedTotalSize;
private int page;
private int pageSize;
}
@@ -0,0 +1,22 @@
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
@AllArgsConstructor
@NoArgsConstructor
public class StorageUsageResponse {
@JsonProperty("user_id")
private Long userId;
@JsonProperty("storage_used")
private Long storageUsed;
@JsonProperty("formatted_storage_used")
private String formattedStorageUsed;
}
@@ -0,0 +1,292 @@
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 ru.soune.nocopy.dto.*;
import ru.soune.nocopy.dto.file.*;
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.util.Arrays;
@Slf4j
@Component
@RequiredArgsConstructor
public class FileEntityHandler implements RequestHandler {
private final FileEntityService fileEntityService;
private final AuthTokenRepository authTokenRepository;
private final ObjectMapper objectMapper;
@Override
public BaseResponse handle(BaseRequest request) {
try {
FileEntityRequest fileRequest = objectMapper.convertValue(
request.getMessageBody(), FileEntityRequest.class);
String action = fileRequest.getAction();
switch (action) {
case "get_file_info":
return handleGetFileInfo(request, fileRequest);
case "get_file_by_session":
return handleGetFileBySession(request, fileRequest);
case "get_user_files":
return handleGetUserFiles(request, fileRequest);
case "search_files":
return handleSearchFiles(request, fileRequest);
case "get_storage_usage":
return handleGetStorageUsage(request, fileRequest);
case "delete_file":
return handleDeleteFile(request, fileRequest);
default:
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"))
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
"Invalid action: " + action,
response);
}
} catch (Exception e) {
log.error("Error in FileEntityHandler", e);
return new BaseResponse(request.getMsgId(),
MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Handler error: " + e.getMessage(),
null);
}
}
private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId());
if (!fileInfo.getUserId().equals(userId)) {
return new BaseResponse(request.getMsgId(),
MessageCode.ACCESS_DENIED.getCode(),
"Access denied",
null);
}
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
fileInfo);
} catch (NotFoundAuthToken e) {
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_TOKEN.getCode(),
"Authentication required",
null);
} catch (Exception e) {
log.error("Error getting file info", e);
return new BaseResponse(request.getMsgId(),
MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Failed to get file info: " + e.getMessage(),
null);
}
}
private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId());
if (!fileInfo.getUserId().equals(userId)) {
return new BaseResponse(request.getMsgId(),
MessageCode.ACCESS_DENIED.getCode(),
"Access denied",
null);
}
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
fileInfo);
} catch (NotFoundAuthToken e) {
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_TOKEN.getCode(),
"Authentication required",
null);
} catch (Exception e) {
log.error("Error getting file by session", e);
return new BaseResponse(request.getMsgId(),
MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Failed to get file: " + e.getMessage(),
null);
}
}
private BaseResponse handleGetUserFiles(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
FileResponse files = fileEntityService.getUserFiles(userId, page, pageSize);
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
files);
} catch (NotFoundAuthToken e) {
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_TOKEN.getCode(),
"Authentication required",
null);
} catch (Exception e) {
log.error("Error getting user files", e);
return new BaseResponse(request.getMsgId(),
MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Failed to get files: " + e.getMessage(),
null);
}
}
private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
if (fileRequest.getQuery() == null || fileRequest.getQuery().isBlank()) {
return handleGetUserFiles(request, fileRequest);
}
FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000);
var filteredFiles = allFiles.getFiles().stream()
.filter(f -> f.getOriginalFileName().toLowerCase().contains(fileRequest.getQuery().toLowerCase()))
.toList();
int start = (page - 1) * pageSize;
int end = Math.min(start + pageSize, filteredFiles.size());
FileListResponse response = FileListResponse.builder()
.files(filteredFiles.subList(start, Math.min(end, filteredFiles.size())))
.totalCount(filteredFiles.size())
.totalSize(allFiles.getTotalSize())
.formattedTotalSize(allFiles.getFormattedTotalSize())
.page(page)
.pageSize(pageSize)
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
response);
} catch (NotFoundAuthToken e) {
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);
}
}
private BaseResponse handleGetStorageUsage(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
long usage = fileEntityService.getUserStorageUsed(userId);
StorageUsageResponse response = StorageUsageResponse.builder()
.userId(userId)
.storageUsed(usage)
.formattedStorageUsed(formatFileSize(usage))
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
response);
} catch (NotFoundAuthToken e) {
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_TOKEN.getCode(),
"Authentication required",
null);
} catch (Exception e) {
log.error("Error getting storage usage", e);
return new BaseResponse(request.getMsgId(),
MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Failed to get storage usage: " + e.getMessage(),
null);
}
}
private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId());
if (!fileInfo.getUserId().equals(userId)) {
return new BaseResponse(request.getMsgId(),
MessageCode.ACCESS_DENIED.getCode(),
"Access denied",
null);
}
fileEntityService.markAsDeleted(fileRequest.getFileId());
DeleteFileResponse 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(),
"Authentication required",
null);
} catch (Exception e) {
log.error("Error deleting file", e);
return new BaseResponse(request.getMsgId(),
MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Failed to delete file: " + e.getMessage(),
null);
}
}
private Long getUserIdFromToken(String token) {
if (token == null || token.isBlank()) {
throw new NotFoundAuthToken("Token is required");
}
if (token.startsWith("Bearer ")) {
token = token.replace("Bearer ", "");
}
AuthToken authToken = authTokenRepository.findByToken(token)
.orElseThrow(() -> new NotFoundAuthToken("Token not found"));
return authToken.getUser().getId();
}
private String formatFileSize(long size) {
if (size < 1024) return size + " B";
int exp = (int) (Math.log(size) / Math.log(1024));
String pre = "KMGTPE".charAt(exp-1) + "";
return String.format("%.1f %sB", size / Math.pow(1024, exp), pre);
}
}
@@ -5,7 +5,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.dto.file.FileEntityResponse;
import ru.soune.nocopy.dto.file.FileListResponse; import ru.soune.nocopy.dto.file.FileResponse;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.FileUploadSession; import ru.soune.nocopy.entity.file.FileUploadSession;
@@ -27,9 +27,6 @@ public class FileEntityService {
private final FileEntityRepository fileEntityRepository; private final FileEntityRepository fileEntityRepository;
/**
* Создает FileEntity на основе завершенной сессии загрузки
*/
@Transactional @Transactional
public FileEntity createFromUploadSession(FileUploadSession session, String checksum) { public FileEntity createFromUploadSession(FileUploadSession session, String checksum) {
log.info("Creating FileEntity for upload session: {}", session.getUploadId()); log.info("Creating FileEntity for upload session: {}", session.getUploadId());
@@ -72,9 +69,7 @@ public class FileEntityService {
} }
} }
/**
* Извлекает расширение файла из имени
*/
private String extractFileExtension(String fileName) { private String extractFileExtension(String fileName) {
int dotIndex = fileName.lastIndexOf('.'); int dotIndex = fileName.lastIndexOf('.');
if (dotIndex > 0 && dotIndex < fileName.length() - 1) { if (dotIndex > 0 && dotIndex < fileName.length() - 1) {
@@ -83,9 +78,6 @@ public class FileEntityService {
return ""; return "";
} }
/**
* Получает FileEntity по ID
*/
@Transactional(readOnly = true) @Transactional(readOnly = true)
public FileEntityResponse getById(String fileId) { public FileEntityResponse getById(String fileId) {
FileEntity fileEntity = fileEntityRepository.findById(fileId) FileEntity fileEntity = fileEntityRepository.findById(fileId)
@@ -94,9 +86,6 @@ public class FileEntityService {
return convertToResponse(fileEntity); return convertToResponse(fileEntity);
} }
/**
* Получает FileEntity по ID сессии загрузки
*/
@Transactional(readOnly = true) @Transactional(readOnly = true)
public FileEntityResponse getByUploadSessionId(String uploadSessionId) { public FileEntityResponse getByUploadSessionId(String uploadSessionId) {
FileEntity fileEntity = fileEntityRepository.findByUploadSessionId(uploadSessionId) FileEntity fileEntity = fileEntityRepository.findByUploadSessionId(uploadSessionId)
@@ -106,9 +95,6 @@ public class FileEntityService {
return convertToResponse(fileEntity); return convertToResponse(fileEntity);
} }
/**
* Получает FileEntity по пути файла
*/
@Transactional(readOnly = true) @Transactional(readOnly = true)
public FileEntityResponse getByFilePath(String filePath) { public FileEntityResponse getByFilePath(String filePath) {
FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath) FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath)
@@ -117,11 +103,9 @@ public class FileEntityService {
return convertToResponse(fileEntity); return convertToResponse(fileEntity);
} }
/**
* Получает все файлы пользователя
*/
@Transactional(readOnly = true) @Transactional(readOnly = true)
public FileListResponse getAllUserFiles(Long userId) { public FileResponse getAllUserFiles(Long userId) {
List<FileEntity> fileEntities = fileEntityRepository.findByUserIdAndStatus( List<FileEntity> fileEntities = fileEntityRepository.findByUserIdAndStatus(
userId, FileStatus.ACTIVE); userId, FileStatus.ACTIVE);
@@ -133,7 +117,7 @@ public class FileEntityService {
.mapToLong(FileEntity::getFileSize) .mapToLong(FileEntity::getFileSize)
.sum(); .sum();
return FileListResponse.builder() return FileResponse.builder()
.files(files) .files(files)
.totalCount(files.size()) .totalCount(files.size())
.totalSize(totalSize) .totalSize(totalSize)
@@ -143,11 +127,9 @@ public class FileEntityService {
.build(); .build();
} }
/**
* Получает файлы пользователя с пагинацией
*/
@Transactional(readOnly = true) @Transactional(readOnly = true)
public FileListResponse getUserFiles(Long userId, int page, int pageSize) { public FileResponse getUserFiles(Long userId, int page, int pageSize) {
List<FileEntity> allFiles = fileEntityRepository.findByUserIdAndStatus( List<FileEntity> allFiles = fileEntityRepository.findByUserIdAndStatus(
userId, FileStatus.ACTIVE); userId, FileStatus.ACTIVE);
@@ -155,7 +137,7 @@ public class FileEntityService {
int end = Math.min(start + pageSize, allFiles.size()); int end = Math.min(start + pageSize, allFiles.size());
if (start >= allFiles.size()) { if (start >= allFiles.size()) {
return FileListResponse.builder() return FileResponse.builder()
.files(List.of()) .files(List.of())
.totalCount(allFiles.size()) .totalCount(allFiles.size())
.totalSize(0) .totalSize(0)
@@ -175,7 +157,7 @@ public class FileEntityService {
.mapToLong(FileEntity::getFileSize) .mapToLong(FileEntity::getFileSize)
.sum(); .sum();
return FileListResponse.builder() return FileResponse.builder()
.files(files) .files(files)
.totalCount(allFiles.size()) .totalCount(allFiles.size())
.totalSize(totalSize) .totalSize(totalSize)
@@ -185,9 +167,6 @@ public class FileEntityService {
.build(); .build();
} }
/**
* Помечает файл как удаленный (мягкое удаление)
*/
@Transactional @Transactional
public void markAsDeleted(String fileId) { public void markAsDeleted(String fileId) {
FileEntity fileEntity = fileEntityRepository.findById(fileId) FileEntity fileEntity = fileEntityRepository.findById(fileId)
@@ -200,18 +179,12 @@ public class FileEntityService {
log.info("FileEntity marked as deleted: {}", fileId); log.info("FileEntity marked as deleted: {}", fileId);
} }
/**
* Получает общий размер файлов пользователя
*/
@Transactional(readOnly = true) @Transactional(readOnly = true)
public long getUserStorageUsed(Long userId) { public long getUserStorageUsed(Long userId) {
Long totalSize = fileEntityRepository.getTotalSizeByUserId(userId); Long totalSize = fileEntityRepository.getTotalSizeByUserId(userId);
return totalSize != null ? totalSize : 0L; return totalSize != null ? totalSize : 0L;
} }
/**
* Поиск файлов пользователя по имени
*/
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<FileEntityResponse> searchFiles(Long userId, String query) { public List<FileEntityResponse> searchFiles(Long userId, String query) {
List<FileEntity> files = fileEntityRepository.searchByFileName(userId, query); List<FileEntity> files = fileEntityRepository.searchByFileName(userId, query);
@@ -222,9 +195,6 @@ public class FileEntityService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* Проверяет, существует ли файл на диске
*/
private boolean checkFileExistsOnDisk(String filePath) { private boolean checkFileExistsOnDisk(String filePath) {
try { try {
return Files.exists(Paths.get(filePath)); return Files.exists(Paths.get(filePath));
@@ -234,9 +204,6 @@ public class FileEntityService {
} }
} }
/**
* Конвертирует FileEntity в DTO
*/
private FileEntityResponse convertToResponse(FileEntity fileEntity) { private FileEntityResponse convertToResponse(FileEntity fileEntity) {
boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath()); boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath());
@@ -260,9 +227,6 @@ public class FileEntityService {
.build(); .build();
} }
/**
* Форматирует размер файла в читаемый вид
*/
private String formatFileSize(long size) { private String formatFileSize(long size) {
if (size < 1024) { if (size < 1024) {
return size + " B"; return size + " B";