Merge branch 'refs/heads/dev' into NCBACK-25

# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/entity/file/FileEntity.java
This commit is contained in:
vladp
2026-01-22 14:15:09 +07:00
54 changed files with 1423 additions and 491 deletions
@@ -0,0 +1,37 @@
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.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.TokenProcessRequest;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.service.auth.AuthService;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthRequestHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final AuthService authService;
@Override
public BaseResponse handle(BaseRequest request) throws NotFoundAuthToken {
TokenProcessRequest tokenProcessRequest = objectMapper.convertValue(request.getMessageBody(),
TokenProcessRequest.class);
authService.useUserAuthToken(tokenProcessRequest.getToken());
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Token processed successfully",
Map.of("token", tokenProcessRequest.getToken()));
}
}
@@ -6,13 +6,12 @@ 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.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.auth.AuthService;
import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileStatsService;
@@ -26,9 +25,9 @@ public class FileEntityHandler implements RequestHandler {
private final FileEntityService fileEntityService;
private final FileStatsService fileStatsService;
private final AuthService authService;
private final AuthTokenRepository authTokenRepository;
private final FileStatsService fileStatsService;
private final ObjectMapper objectMapper;
@@ -62,7 +61,8 @@ public class FileEntityHandler implements RequestHandler {
.action(action)
.availableActions(Arrays.asList(
"file_info", "file_by_session", "user_files",
"search_files", "storage_usage", "delete_file"))
"search_files", "storage_usage", "delete_file",
"user_files_info"))
.build();
return new BaseResponse(request.getMsgId(),
@@ -80,7 +80,7 @@ public class FileEntityHandler implements RequestHandler {
}
private BaseResponse handleGetFilesUserInfo(BaseRequest request, FileEntityRequest fileRequest) {
Long userId = getUserIdFromToken(fileRequest.getToken());
Long userId = authService.useUserAuthToken(fileRequest.getToken());
FileInfoUserResponse userFileStats = fileStatsService.getUserFileStats(userId);
return new BaseResponse(request.getMsgId(),
@@ -91,7 +91,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
Long userId = authService.useUserAuthToken(fileRequest.getToken());
FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion());
if (!fileInfo.getUserId().equals(userId)) {
@@ -122,7 +122,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
Long userId = authService.useUserAuthToken(fileRequest.getToken());
FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId(),
request.getVersion());
@@ -154,7 +154,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleGetUserFiles(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
Long userId = authService.useUserAuthToken(fileRequest.getToken());
int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
@@ -181,7 +181,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
Long userId = authService.useUserAuthToken(fileRequest.getToken());
int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
@@ -228,7 +228,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleGetStorageUsage(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
Long userId = authService.useUserAuthToken(fileRequest.getToken());
long usage = fileEntityService.getUserStorageUsed(userId);
StorageUsageResponse response = StorageUsageResponse.builder()
@@ -258,7 +258,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = getUserIdFromToken(fileRequest.getToken());
Long userId = authService.useUserAuthToken(fileRequest.getToken());
String fileId = fileRequest.getFileId();
FileEntityResponse fileInfo = fileEntityService.getById(fileId, request.getVersion());
DeleteFileResponse response;
@@ -305,20 +305,6 @@ public class FileEntityHandler implements RequestHandler {
}
}
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));
@@ -17,6 +17,7 @@ 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.auth.AuthService;
import ru.soune.nocopy.service.file.FileUploadService;
import java.util.*;
@@ -28,6 +29,8 @@ import java.util.stream.Collectors;
public class FileUploadHandler implements RequestHandler {
private final FileUploadService fileUploadService;
private final AuthService authService;
private final AuthTokenRepository authTokenRepository;
private final FileUploadSessionRepository fileUploadSessionRepository;
@@ -86,10 +89,9 @@ public class FileUploadHandler implements RequestHandler {
MessageCode.INVALID_TOKEN.getDescription(), Map.of("token", token));
}
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token not found"));
Long userId = authService.useUserAuthToken(token);
BindingResult bindingResult = new BeanPropertyBindingResult(fileRequest, "fileRequest");
fileUploadRequestValidator.validate(fileRequest, bindingResult);
fileUploadRequestValidator.validate(fileRequest, bindingResult);
if (bindingResult.hasErrors()) {
Map<String, String> fieldErrors = bindingResult.getFieldErrors()
@@ -105,7 +107,7 @@ public class FileUploadHandler implements RequestHandler {
}
FileUploadSession session = fileUploadService.initUpload(
authToken.getUser().getId(),
userId,
fileRequest.getFileName(),
fileRequest.getFileType(),
fileRequest.getExtension(),
@@ -175,40 +177,13 @@ public class FileUploadHandler implements RequestHandler {
}
}
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));
}
@@ -223,7 +198,6 @@ public class FileUploadHandler implements RequestHandler {
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);
@@ -5,15 +5,16 @@ import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.exception.*;
import java.util.Map;
@RestControllerAdvice
@AllArgsConstructor
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@@ -81,4 +82,12 @@ public class GlobalExceptionHandler {
"message" ,ex.getMessage()
));
}
@ExceptionHandler(DuplicateImageException.class)
@ResponseBody
public ResponseEntity<BaseResponse> handleDuplicateImage(DuplicateImageException e) {
return ResponseEntity.ok().body(new BaseResponse(
20004, MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
"Duplicate image detected", e.getMessage()));
}
}
@@ -0,0 +1,36 @@
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.Autowired;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.ImageSearchRequest;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.service.YandexSearchService;
@Slf4j
@Component
@RequiredArgsConstructor
public class ImageFoundRequestHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final YandexSearchService yandexSearchService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
ImageSearchRequest imageSearchRequest = objectMapper.convertValue(request.getMessageBody(),
ImageSearchRequest.class);
String fileId = imageSearchRequest.getFileId();
YandexSearchResponse response = yandexSearchService.searchByFileEntity(fileId);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), response);
}
}
@@ -7,7 +7,7 @@ import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.entity.AuthToken;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.AuthService;
import ru.soune.nocopy.service.auth.AuthService;
import java.util.Arrays;
import java.util.Map;
@@ -17,7 +17,9 @@ import java.util.Map;
public class LoginRequestHandler implements RequestHandler {
private final UserRepository userRepository;
private final AuthService authService;
private final ObjectMapper objectMapper;
@Override
@@ -34,9 +36,12 @@ public class LoginRequestHandler implements RequestHandler {
}
AuthToken authToken = authService.login(loginRequest);
String token = authToken.getToken();
LoginAnswer loginAnswer = new LoginAnswer();
loginAnswer.setToken(authToken.getToken());
loginAnswer.setToken(token);
authService.useUserAuthToken(token);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), loginAnswer);
@@ -9,7 +9,7 @@ import ru.soune.nocopy.entity.User;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.AuthService;
import ru.soune.nocopy.service.auth.AuthService;
import java.util.Objects;
import java.util.Optional;
@@ -12,7 +12,7 @@ import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.ValidationException;
import ru.soune.nocopy.handler.validator.RegRequestValidator;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.AuthService;
import ru.soune.nocopy.service.auth.AuthService;
import java.util.Arrays;
import java.util.Map;
@@ -52,9 +52,11 @@ public class RegRequestHandler implements RequestHandler {
}
AuthToken authToken = authService.register(regRequest);
String token = authToken.getToken();
authService.useUserAuthToken(token);
RegAnswer regAnswer = new RegAnswer();
regAnswer.setToken(authToken.getToken());
regAnswer.setToken(token);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), regAnswer);
@@ -10,7 +10,7 @@ import java.util.*;
@Component
public class RegRequestValidator implements Validator {
private static final String COMPANY_REGEX = "^[a-zA-Zа-яА-ЯёЁ0-9\\s\\-&.,'()]{0,200}$";
private static final String COMPANY_REGEX = "^[a-zA-Zа-яА-ЯёЁ0-9\\s\\-&.,'()\"\"«»„‟”“”‘'’‹›❛❜❝❞〝〞〟]{1,200}$";
private static final String NAME_REGEX = "^[a-zA-Zа-яА-ЯёЁ\\s\\-'.]{2,100}$";
@@ -71,21 +71,6 @@ public class RegRequestValidator implements Validator {
errors.rejectValue("companyName", "companyName.invalid.chars",
"Company name contains invalid characters");
}
validateForbiddenWords(trimmedCompany, "companyName", errors,
Arrays.asList("admin", "root", "system", "test"));
}
private void validateForbiddenWords(String text, String fieldName,
Errors errors, List<String> forbiddenWords) {
String lowerText = text.toLowerCase();
for (String word : forbiddenWords) {
if (lowerText.contains(word)) {
errors.rejectValue(fieldName, fieldName + ".forbidden.word",
"Contains forbidden word: " + word);
break;
}
}
}
private void validateEmail(String email, Errors errors) {