5 Commits
Author SHA1 Message Date
vladp 7b1ed5fe67 NCBACK-3
Test Workflow / test (push) Successful in 3s
2026-01-10 00:35:29 +07:00
vladp b3a3d57ee3 NCBACK-3
Test Workflow / test (push) Successful in 3s
2026-01-09 23:41:19 +07:00
vladp edd508e5d8 Merge branch 'dev' into NCBACK-3
Test Workflow / test (push) Successful in 5s
# Conflicts:
#	docker-compose.yaml
#	src/main/java/ru/soune/nocopy/dto/MessageCode.java
2026-01-09 12:34:24 +07:00
vladp ffb147d716 NCBACK-3 Add found by yandex image
Test Workflow / test (push) Successful in 4s
2025-12-22 14:42:27 +07:00
vladp 647fd6e1a7 NCBACK-3 Add found by yandex image
Test Workflow / test (push) Successful in 3s
2025-12-22 14:07:44 +07:00
24 changed files with 312 additions and 162 deletions
+4
View File
@@ -31,6 +31,10 @@ dependencies {
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
implementation 'commons-validator:commons-validator:1.7'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.0'
implementation 'tools.jackson.core:jackson-core:3.0.3'
implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
annotationProcessor 'org.projectlombok:lombok'
+4
View File
@@ -65,7 +65,11 @@ services:
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
POSTGRES_HOST: db
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q
YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd
YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"
STORAGE_SERVICE_URL: http://storage:8081
SPRING_PROFILES_ACTIVE: docker
depends_on:
- db
ports:
@@ -17,7 +17,7 @@ public class HandlerConfig {
FileUploadHandler upload,
FileEntityHandler file,
LogoutRequestHandler logoutHandler,
AuthRequestHandler authRequestHandler
ImageFoundRequestHandler imageFoundRequestHandler
) {
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login);
@@ -25,7 +25,7 @@ public class HandlerConfig {
map.put(20004, upload);
map.put(20005, file);
map.put(20006, logoutHandler);
map.put(20008, authRequestHandler);
map.put(20007, imageFoundRequestHandler);
return map;
}
@@ -35,6 +35,8 @@ public class JacksonConfig {
mapper.registerModule(javaTimeModule);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
return mapper;
}
@@ -19,11 +19,12 @@ import ru.soune.nocopy.dto.file.ChunkUploadResponse;
import ru.soune.nocopy.dto.file.CompleteUploadResponse;
import ru.soune.nocopy.dto.file.FileEntityResponse;
import ru.soune.nocopy.dto.file.UploadProgress;
import ru.soune.nocopy.entity.AuthToken;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.UploadStatus;
import ru.soune.nocopy.exception.*;
import ru.soune.nocopy.handler.*;
import ru.soune.nocopy.service.auth.AuthService;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileUploadService;
@@ -47,7 +48,7 @@ public class ApiController {
private final FileEntityService fileEntityService;
private final AuthService authService;
private final AuthTokenRepository authTokenRepository;
@PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@@ -70,9 +71,6 @@ public class ApiController {
return ResponseEntity.ok().body(response);
} catch (ValidationException e) {
return createValidationErrorResponse(e.getBindingResult(), e.getMsgId());
} catch (NotFoundAuthToken e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotValidFieldException e) {
throw e;
} catch (Exception e) {
@@ -259,7 +257,7 @@ public class ApiController {
errorData));
}
Long userId = authService.useUserAuthToken(tokenHeader);
Long userId = getUserIdFromToken(tokenHeader);
FileEntityResponse entityResponse = fileEntityService.getById(fileId, version);
if (!entityResponse.getUserId().equals(userId)) {
@@ -370,15 +368,10 @@ public class ApiController {
return contentType;
}
private boolean isPreviewSupported(String mimeType) {
if (mimeType == null) {
return false;
}
return mimeType.startsWith("image") ||
mimeType.startsWith("text") ||
mimeType.equals("pdf") ||
mimeType.startsWith("video") ||
mimeType.startsWith("audio");
private Long getUserIdFromToken(String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
AuthToken authToken = authTokenRepository.findByToken(token)
.orElseThrow(() -> new NotFoundAuthToken("Token not found"));
return authToken.getUser().getId();
}
}
@@ -10,6 +10,7 @@ public enum MessageCode {
FILE_UPLOAD_ERROR(2, "File upload error"),
FILE_DOWNLOAD_ERROR(2, "File download error"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
IMAGE_FOUND_ERROR(2, "Image found error"),
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"),
@@ -18,7 +19,6 @@ public enum MessageCode {
AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "),
AUTH_TOKEN_MISMATCH(4, "Token mismatch"),
AUTH_TOKEN_NOT_FOUND(4, "Token not found"),
FILE_NOT_FOUND(4, "File not found"),
AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match");
@@ -1,11 +0,0 @@
package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class TokenProcessRequest {
@JsonProperty("token")
private String token;
}
@@ -0,0 +1,10 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class ImageSearchRequest {
@JsonProperty("file_id")
private String fileId;
}
@@ -0,0 +1,36 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class YandexSearchResponse {
@JsonProperty("images")
private List<ImageResult> images;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ImageResult {
@JsonProperty("url")
private String url;
@JsonProperty("pageUrl")
private String pageUrl;
@JsonProperty("pageTitle")
private String pageTitle;
@JsonProperty("width")
private Integer width;
@JsonProperty("height")
private Integer height;
@JsonProperty("host")
private String host;
}
}
@@ -30,7 +30,7 @@ public class AuthToken {
private String token;
@Column(name = "expires_at", nullable = false)
private LocalDateTime expiresAt = LocalDateTime.now().plusHours(1);
private LocalDateTime expiresAt = LocalDateTime.now().plusDays(30);
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
@@ -41,4 +41,8 @@ public class AuthToken {
@Column(name = "is_active")
private Boolean isActive = true;
public boolean isValid() {
return Boolean.TRUE.equals(isActive) && expiresAt.isAfter(LocalDateTime.now());
}
}
@@ -1,37 +0,0 @@
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,13 @@ 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,8 +26,6 @@ public class FileEntityHandler implements RequestHandler {
private final FileEntityService fileEntityService;
private final AuthService authService;
private final FileStatsService fileStatsService;
private final AuthTokenRepository authTokenRepository;
@@ -93,7 +91,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = authService.useUserAuthToken(fileRequest.getToken());
Long userId = getUserIdFromToken(fileRequest.getToken());
FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion());
if (!fileInfo.getUserId().equals(userId)) {
@@ -124,7 +122,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = authService.useUserAuthToken(fileRequest.getToken());
Long userId = getUserIdFromToken(fileRequest.getToken());
FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId(),
request.getVersion());
@@ -156,7 +154,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleGetUserFiles(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = authService.useUserAuthToken(fileRequest.getToken());
Long userId = getUserIdFromToken(fileRequest.getToken());
int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
@@ -183,7 +181,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = authService.useUserAuthToken(fileRequest.getToken());
Long userId = getUserIdFromToken(fileRequest.getToken());
int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
@@ -230,7 +228,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleGetStorageUsage(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = authService.useUserAuthToken(fileRequest.getToken());
Long userId = getUserIdFromToken(fileRequest.getToken());
long usage = fileEntityService.getUserStorageUsed(userId);
StorageUsageResponse response = StorageUsageResponse.builder()
@@ -260,7 +258,7 @@ public class FileEntityHandler implements RequestHandler {
private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = authService.useUserAuthToken(fileRequest.getToken());
Long userId = getUserIdFromToken(fileRequest.getToken());
String fileId = fileRequest.getFileId();
FileEntityResponse fileInfo = fileEntityService.getById(fileId, request.getVersion());
DeleteFileResponse response;
@@ -307,6 +305,20 @@ 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,7 +17,6 @@ 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.*;
@@ -29,8 +28,6 @@ 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;
@@ -89,9 +86,10 @@ public class FileUploadHandler implements RequestHandler {
MessageCode.INVALID_TOKEN.getDescription(), Map.of("token", token));
}
Long userId = authService.useUserAuthToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token not found"));
BindingResult bindingResult = new BeanPropertyBindingResult(fileRequest, "fileRequest");
fileUploadRequestValidator.validate(fileRequest, bindingResult);
fileUploadRequestValidator.validate(fileRequest, bindingResult);
if (bindingResult.hasErrors()) {
Map<String, String> fieldErrors = bindingResult.getFieldErrors()
@@ -107,7 +105,7 @@ public class FileUploadHandler implements RequestHandler {
}
FileUploadSession session = fileUploadService.initUpload(
userId,
authToken.getUser().getId(),
fileRequest.getFileName(),
fileRequest.getFileType(),
fileRequest.getExtension(),
@@ -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.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;
@Autowired
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.auth.AuthService;
import ru.soune.nocopy.service.AuthService;
import java.util.Arrays;
import java.util.Map;
@@ -17,9 +17,7 @@ import java.util.Map;
public class LoginRequestHandler implements RequestHandler {
private final UserRepository userRepository;
private final AuthService authService;
private final ObjectMapper objectMapper;
@Override
@@ -36,12 +34,9 @@ public class LoginRequestHandler implements RequestHandler {
}
AuthToken authToken = authService.login(loginRequest);
String token = authToken.getToken();
LoginAnswer loginAnswer = new LoginAnswer();
loginAnswer.setToken(token);
authService.useUserAuthToken(token);
loginAnswer.setToken(authToken.getToken());
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.auth.AuthService;
import ru.soune.nocopy.service.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.auth.AuthService;
import ru.soune.nocopy.service.AuthService;
import java.util.Arrays;
import java.util.Map;
@@ -52,11 +52,9 @@ public class RegRequestHandler implements RequestHandler {
}
AuthToken authToken = authService.register(regRequest);
String token = authToken.getToken();
authService.useUserAuthToken(token);
RegAnswer regAnswer = new RegAnswer();
regAnswer.setToken(token);
regAnswer.setToken(authToken.getToken());
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), regAnswer);
@@ -4,12 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.AuthToken;
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.util.Optional;
@Repository
public interface AuthTokenRepository extends JpaRepository<AuthToken, Long> {
Optional<AuthToken> findByLastUsedAtAndExpiresAtBefore(LocalDateTime lastUsedAt, LocalDateTime expiresAtBefore);
Optional<AuthToken> findByLastUsedAtBefore(LocalDateTime lastUsedAt);
Optional<AuthToken> findByTokenAndExpiresAtAfter(String token, LocalDate expiresAtAfter);
Optional<AuthToken> findByToken(String token);
}
@@ -14,6 +14,7 @@ import java.util.Optional;
public interface FileEntityRepository extends JpaRepository<FileEntity, String> {
List<FileEntity> findByUserId(Long userId);
Optional<FileEntity> findByUserIdAndChecksum(Long userId, String imageHash);
List<FileEntity> findByUserIdAndStatus(Long userId, FileStatus status);
@@ -1,13 +1,13 @@
package ru.soune.nocopy.service.auth;
package ru.soune.nocopy.service;
import lombok.RequiredArgsConstructor;
import org.springframework.context.MessageSource;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.entity.AuthToken;
import ru.soune.nocopy.entity.User;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.UserRepository;
@@ -29,6 +29,8 @@ public class AuthService {
private final PasswordEncoder passwordEncoder;
private final MessageSource messageSource;
private final SecureRandom secureRandom = new SecureRandom();
@Transactional
@@ -79,25 +81,6 @@ public class AuthService {
return authTokenRepository.save(authToken);
}
@Transactional
public Long useUserAuthToken(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"));
authToken.setLastUsedAt(LocalDateTime.now());
authTokenRepository.save(authToken);
return authToken.getUser().getId();
}
@Transactional
public void logout(String token) {
authTokenRepository.findByToken(token)
@@ -0,0 +1,162 @@
package ru.soune.nocopy.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.exception.FileEntityNotFoundException;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.FileEntityRepository;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class YandexSearchService {
private final FileEntityRepository fileEntityRepository;
private final ObjectMapper objectMapper;
@Value("${YANDEX_API_KEY}")
private String apiKey;
@Value("${YANDEX_FOLDER_ID}")
private String folderId;
@Value("${YANDEX_SEARCH_URL}")
private String searchUrl;
@PostConstruct
public void init() {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
public YandexSearchResponse searchByFileEntity(String fileId) throws IOException {
FileEntity fileEntity = fileEntityRepository.findById(fileId)
.orElseThrow(() -> {
throw new NotValidFieldException("File not found", new BaseResponse(20007,
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
Map.of("fileId",fileId)));
});
byte[] fileBytes;
if (!isImageFile(fileEntity)) {
log.error("File not image: {}", fileEntity.getMimeType());
throw new NotValidFieldException("File not image", new BaseResponse(20007,
MessageCode.INVALID_FIELD.getCode(), MessageCode.INVALID_FIELD.getDescription(),
Map.of("file_type", fileEntity.getMimeType())));
}
try {
fileBytes = readFileFromDisk(fileEntity);
} catch (IOException e) {
throw new NotValidFieldException("File not found or cannot read file", new BaseResponse(20007,
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
Map.of("fileId", fileId,
"filePath", fileEntity.getFilePath())));
}
return callYandexApi(fileBytes);
}
private boolean isImageFile(FileEntity fileEntity) {
String mimeType = fileEntity.getMimeType();
return mimeType != null && mimeType.startsWith("image");
}
private byte[] readFileFromDisk(FileEntity fileEntity) throws IOException {
Path filePath = Path.of(fileEntity.getFilePath());
if (!Files.exists(filePath)) {
throw new IOException("File not found: " + fileEntity.getFilePath());
}
if (!Files.isReadable(filePath)) {
throw new IOException("Cannot read file: " + fileEntity.getFilePath());
}
return Files.readAllBytes(filePath);
}
private YandexSearchResponse callYandexApi(byte[] imageBytes) throws IOException {
String imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
String jsonRequest = String.format("{\"folderId\":\"%s\",\"data\":\"%s\",\"page\":0,\"showSimilarImages\":true}",
folderId, imageBase64);
URL url = new URL(searchUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
connection.setRequestMethod("POST");
connection.setRequestProperty("Authorization", "Api-Key " + apiKey);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonRequest.getBytes("utf-8");
os.write(input, 0, input.length);
os.flush();
}
int responseCode = connection.getResponseCode();
String responseBody;
if (responseCode == HttpURLConnection.HTTP_OK) {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(connection.getInputStream(), "utf-8"))) {
responseBody = readAll(br);
}
} else {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(connection.getErrorStream(), "utf-8"))) {
responseBody = readAll(br);
}
throw new IOException("Error Yandex API: " + responseCode + " - " + responseBody);
}
return parseJsonResponse(responseBody);
} finally {
connection.disconnect();
}
}
private String readAll(BufferedReader reader) throws IOException {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
private YandexSearchResponse parseJsonResponse(String json) {
YandexSearchResponse response = null;
try {
response = objectMapper.readValue(json, YandexSearchResponse.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return response;
}
}
@@ -1,38 +0,0 @@
package ru.soune.nocopy.service.auth;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.entity.AuthToken;
import ru.soune.nocopy.repository.AuthTokenRepository;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class CleanupTokenSessionsService {
@Autowired
private AuthTokenRepository authTokenRepository;
@Value("${file.storage.auth-token-life-hours}")
private int authTokenLife;
@Transactional
@Scheduled(fixedDelay = 30000)
public void cleanupExpiredFiles() {
LocalDateTime hourBefore = LocalDateTime.now().minusHours(authTokenLife);
List<AuthToken> tokensForDelete = authTokenRepository.findByLastUsedAtBefore(hourBefore).stream().toList();
List<AuthToken> tokensDeleteWithNullUpdate = authTokenRepository.findByLastUsedAtAndExpiresAtBefore(
null, hourBefore).stream().toList();
authTokenRepository.deleteAll(tokensForDelete);
authTokenRepository.deleteAll(tokensDeleteWithNullUpdate);
}
}
-1
View File
@@ -31,7 +31,6 @@ file:
max-retry-attempts: ${MAX_RETRY_ATTEMPTS:3}
chunk-timeout-ms: ${CHUNK_TIMEOUT_MS:300000} # 5 минут
temp-ttl-hours: ${TEMP_TTL_HOURS:72} # 3 дня
auth-token-life-hours: ${AUTH_TOKEN_LIFE:1} # 1 час
session-expiry-hours: ${SESSION_EXPIRY_HOURS:24}
security:
@@ -15,7 +15,6 @@ import ru.soune.nocopy.exception.NotValidationPasswordException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.auth.AuthService;
import java.util.Optional;