From 647fd6e1a7143d059afb0a8f02cf9696fddf621d Mon Sep 17 00:00:00 2001 From: vladp Date: Mon, 22 Dec 2025 14:07:44 +0700 Subject: [PATCH 01/16] NCBACK-3 Add found by yandex image --- build.gradle | 4 + docker-compose.yaml | 2 + .../nocopy/configuration/HandlerConfig.java | 4 +- .../nocopy/configuration/JacksonConfig.java | 2 + .../configuration/file/YandexConfig.java | 14 ++ .../java/ru/soune/nocopy/dto/MessageCode.java | 1 + .../nocopy/dto/file/ImageSearchRequest.java | 10 ++ .../nocopy/dto/file/YandexSearchResponse.java | 36 +++++ .../handler/ImageFoundRequestHandler.java | 36 +++++ .../repository/FileEntityRepository.java | 1 + .../nocopy/service/YandexSearchService.java | 149 ++++++++++++++++++ src/main/resources/application.yaml | 5 + 12 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/soune/nocopy/configuration/file/YandexConfig.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/ImageSearchRequest.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/YandexSearchResponse.java create mode 100644 src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java create mode 100644 src/main/java/ru/soune/nocopy/service/YandexSearchService.java diff --git a/build.gradle b/build.gradle index d42173a..111da7e 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/docker-compose.yaml b/docker-compose.yaml index d3f9924..eb4724b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -30,6 +30,8 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_PORT: 5432 POSTGRES_HOST: db + YANDEX_SEARCH_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q + YANDEX_SEARCH_FOLDER_ID: b1gokpdbm6qfpsou8pcd depends_on: - db ports: diff --git a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java index 5b3c744..674b27c 100644 --- a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java @@ -16,7 +16,8 @@ public class HandlerConfig { LoginRequestHandler login, FileUploadHandler upload, FileEntityHandler file, - LogoutRequestHandler logoutHandler + LogoutRequestHandler logoutHandler, + ImageFoundRequestHandler imageFoundRequestHandler ) { Map map = new HashMap<>(); map.put(20001, login); @@ -24,6 +25,7 @@ public class HandlerConfig { map.put(20004, upload); map.put(20005, file); map.put(20006, logoutHandler); + map.put(20007, imageFoundRequestHandler); return map; } diff --git a/src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java b/src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java index 99c5c00..db00da1 100644 --- a/src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java @@ -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; } diff --git a/src/main/java/ru/soune/nocopy/configuration/file/YandexConfig.java b/src/main/java/ru/soune/nocopy/configuration/file/YandexConfig.java new file mode 100644 index 0000000..e9f1576 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/configuration/file/YandexConfig.java @@ -0,0 +1,14 @@ +package ru.soune.nocopy.configuration.file; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "yandex") +public class YandexConfig { + private String apiKey; + private String folderId; + private String searchUrl; +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/dto/MessageCode.java b/src/main/java/ru/soune/nocopy/dto/MessageCode.java index a3ac162..ee9d40b 100644 --- a/src/main/java/ru/soune/nocopy/dto/MessageCode.java +++ b/src/main/java/ru/soune/nocopy/dto/MessageCode.java @@ -8,6 +8,7 @@ public enum MessageCode { INVALID_TOKEN(2, "Invalid token"), INVALID_ACTION(2, "Invalid action"), FILE_UPLOAD_ERROR(2, "File upload error"), + 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"), diff --git a/src/main/java/ru/soune/nocopy/dto/file/ImageSearchRequest.java b/src/main/java/ru/soune/nocopy/dto/file/ImageSearchRequest.java new file mode 100644 index 0000000..f943989 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/ImageSearchRequest.java @@ -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; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/YandexSearchResponse.java b/src/main/java/ru/soune/nocopy/dto/file/YandexSearchResponse.java new file mode 100644 index 0000000..760440f --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/YandexSearchResponse.java @@ -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 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; + } +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java new file mode 100644 index 0000000..1b7b0fd --- /dev/null +++ b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java @@ -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.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); + } +} diff --git a/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java b/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java index 3ee41cb..3214e9d 100644 --- a/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java @@ -14,6 +14,7 @@ import java.util.Optional; public interface FileEntityRepository extends JpaRepository { List findByUserId(Long userId); + Optional findByUserIdAndChecksum(Long userId, String imageHash); List findByUserIdAndStatus(Long userId, FileStatus status); diff --git a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java new file mode 100644 index 0000000..611036c --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java @@ -0,0 +1,149 @@ +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.file.YandexSearchResponse; +import ru.soune.nocopy.entity.file.FileEntity; +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; + +@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(() -> { + log.error("File not found: {}", fileId); + return new RuntimeException("ФFile not found: " + fileId); + }); + + + if (!isImageFile(fileEntity)) { + log.error("File not image: {}", fileEntity.getMimeType()); + throw new RuntimeException("File not image: " + fileId); + } + + byte[] fileBytes = readFileFromDisk(fileEntity); + log.info("Прочитано {} байт с диска", fileBytes.length); + + 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.setRequestProperty("User-Agent", "Mozilla/5.0"); + 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("Ошибка 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; + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 08326e1..45fcaaa 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -38,6 +38,11 @@ security: allowed-headers: "*" allow-credentials: true +yandex: + api-key: ${YANDEX_SEARCH_API_KEY} + folder-id: ${YANDEX_SEARCH_FOLDER_ID} + search-url: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image" + server: port: ${SERVER_PORT:8080} From ffb147d716488d696315ec84a067a333ad05d434 Mon Sep 17 00:00:00 2001 From: vladp Date: Mon, 22 Dec 2025 14:42:27 +0700 Subject: [PATCH 02/16] NCBACK-3 Add found by yandex image --- .../handler/ImageFoundRequestHandler.java | 5 ++--- .../nocopy/service/YandexSearchService.java | 20 ++++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java index 1b7b0fd..301bb10 100644 --- a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java @@ -22,9 +22,8 @@ public class ImageFoundRequestHandler implements RequestHandler { @Override public BaseResponse handle(BaseRequest request) throws Exception { - - ImageSearchRequest imageSearchRequest = objectMapper.convertValue( - request.getMessageBody(), ImageSearchRequest.class); + ImageSearchRequest imageSearchRequest = objectMapper.convertValue(request.getMessageBody(), + ImageSearchRequest.class); String fileId = imageSearchRequest.getFileId(); diff --git a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java index 611036c..3fd8c60 100644 --- a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java +++ b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java @@ -8,8 +8,12 @@ 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.*; @@ -18,6 +22,7 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.Base64; +import java.util.Map; @Slf4j @Service @@ -45,18 +50,19 @@ public class YandexSearchService { public YandexSearchResponse searchByFileEntity(String fileId) throws IOException { FileEntity fileEntity = fileEntityRepository.findById(fileId) .orElseThrow(() -> { - log.error("File not found: {}", fileId); - return new RuntimeException("ФFile not found: " + fileId); + throw new NotValidFieldException("File not found", new BaseResponse(20007, + MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(), + Map.of("fileId",fileId))); }); - if (!isImageFile(fileEntity)) { log.error("File not image: {}", fileEntity.getMimeType()); - throw new RuntimeException("File not image: " + fileId); + throw new NotValidFieldException("File not image", new BaseResponse(20007, + MessageCode.INVALID_FIELD.getCode(), MessageCode.INVALID_FIELD.getDescription(), + Map.of("file_type", fileEntity.getMimeType()))); } byte[] fileBytes = readFileFromDisk(fileEntity); - log.info("Прочитано {} байт с диска", fileBytes.length); return callYandexApi(fileBytes); } @@ -93,7 +99,7 @@ public class YandexSearchService { connection.setRequestProperty("Authorization", "Api-Key " + apiKey); connection.setRequestProperty("Content-Type", "application/json"); connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("User-Agent", "Mozilla/5.0"); +// connection.setRequestProperty("User-Agent", "Mozilla/5.0"); connection.setConnectTimeout(30000); connection.setReadTimeout(30000); connection.setDoOutput(true); @@ -117,7 +123,7 @@ public class YandexSearchService { new InputStreamReader(connection.getErrorStream(), "utf-8"))) { responseBody = readAll(br); } - throw new IOException("Ошибка Yandex API: " + responseCode + " - " + responseBody); + throw new IOException("Error Yandex API: " + responseCode + " - " + responseBody); } return parseJsonResponse(responseBody); From d896f238724218b2763649561aa48fabe506d46d Mon Sep 17 00:00:00 2001 From: vladp Date: Fri, 9 Jan 2026 23:05:37 +0700 Subject: [PATCH 03/16] add user_files_info for avaibale actions --- src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java index cacf0eb..a7f9c73 100644 --- a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java @@ -62,7 +62,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(), From b3a3d57ee3e8442b982600b70a942fa6e808d3f3 Mon Sep 17 00:00:00 2001 From: vladp Date: Fri, 9 Jan 2026 23:41:19 +0700 Subject: [PATCH 04/16] NCBACK-3 --- .../nocopy/configuration/file/YandexConfig.java | 14 -------------- .../soune/nocopy/service/YandexSearchService.java | 11 +++++++++-- 2 files changed, 9 insertions(+), 16 deletions(-) delete mode 100644 src/main/java/ru/soune/nocopy/configuration/file/YandexConfig.java diff --git a/src/main/java/ru/soune/nocopy/configuration/file/YandexConfig.java b/src/main/java/ru/soune/nocopy/configuration/file/YandexConfig.java deleted file mode 100644 index e9f1576..0000000 --- a/src/main/java/ru/soune/nocopy/configuration/file/YandexConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.soune.nocopy.configuration.file; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Data -@Configuration -@ConfigurationProperties(prefix = "yandex") -public class YandexConfig { - private String apiKey; - private String folderId; - private String searchUrl; -} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java index 3fd8c60..6c3d32f 100644 --- a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java +++ b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java @@ -54,6 +54,7 @@ public class YandexSearchService { 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()); @@ -62,7 +63,14 @@ public class YandexSearchService { Map.of("file_type", fileEntity.getMimeType()))); } - byte[] fileBytes = readFileFromDisk(fileEntity); + 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); } @@ -99,7 +107,6 @@ public class YandexSearchService { connection.setRequestProperty("Authorization", "Api-Key " + apiKey); connection.setRequestProperty("Content-Type", "application/json"); connection.setRequestProperty("Accept", "application/json"); -// connection.setRequestProperty("User-Agent", "Mozilla/5.0"); connection.setConnectTimeout(30000); connection.setReadTimeout(30000); connection.setDoOutput(true); From 7b1ed5fe679b3a0e7bf22a0555d3ee3f4d34ba21 Mon Sep 17 00:00:00 2001 From: vladp Date: Sat, 10 Jan 2026 00:35:29 +0700 Subject: [PATCH 05/16] NCBACK-3 --- docker-compose.yaml | 6 ++++-- .../ru/soune/nocopy/handler/ImageFoundRequestHandler.java | 2 ++ .../java/ru/soune/nocopy/service/YandexSearchService.java | 6 +++--- src/main/resources/application.yaml | 5 ----- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 0a65adf..06a38f1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -65,9 +65,11 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_PORT: 5432 POSTGRES_HOST: db - YANDEX_SEARCH_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q - YANDEX_SEARCH_FOLDER_ID: b1gokpdbm6qfpsou8pcd + 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: diff --git a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java index 301bb10..dfe67cc 100644 --- a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java @@ -3,6 +3,7 @@ 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; @@ -18,6 +19,7 @@ public class ImageFoundRequestHandler implements RequestHandler { private final ObjectMapper objectMapper; + @Autowired private final YandexSearchService yandexSearchService; @Override diff --git a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java index 6c3d32f..66f3f18 100644 --- a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java +++ b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java @@ -32,13 +32,13 @@ public class YandexSearchService { private final FileEntityRepository fileEntityRepository; private final ObjectMapper objectMapper; - @Value("${yandex.api-key}") + @Value("${YANDEX_API_KEY}") private String apiKey; - @Value("${yandex.folder-id}") + @Value("${YANDEX_FOLDER_ID}") private String folderId; - @Value("${yandex.search-url}") + @Value("${YANDEX_SEARCH_URL}") private String searchUrl; @PostConstruct diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b58b4bc..da03a23 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -40,11 +40,6 @@ security: allowed-headers: "*" allow-credentials: true -yandex: - api-key: ${YANDEX_SEARCH_API_KEY} - folder-id: ${YANDEX_SEARCH_FOLDER_ID} - search-url: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image" - server: port: ${SERVER_PORT:8080} From 920cde3cdf1ec8f2e21fd5de1f027c2012cab4be Mon Sep 17 00:00:00 2001 From: vladp Date: Sat, 10 Jan 2026 00:51:03 +0700 Subject: [PATCH 06/16] Dev --- Dockerfile | 2 +- docker-compose.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6cd62ca..9fda8a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ RUN mkdir -p /data/uploads && chmod 755 /data/uploads EXPOSE 8080 -CMD ["java", "-jar", "app.jar"] +ENTRYPOINT ["sh", "-c", "exec java ${JAVA_OPTS} -jar app.jar"] diff --git a/docker-compose.yaml b/docker-compose.yaml index 06a38f1..7ee937f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -65,11 +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 + + JAVA_OPTS: "-DYANDEX_API_KEY=AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q -DYANDEX_FOLDER_ID=b1gokpdbm6qfpsou8pcd -DYANDEX_SEARCH_URL='https://searchapi.api.cloud.yandex.net/v2/image/search_by_image'" + depends_on: - db ports: From eb12e219ec161b3c04d7cf625fdd34d17e93c08c Mon Sep 17 00:00:00 2001 From: vladp Date: Sat, 10 Jan 2026 00:54:49 +0700 Subject: [PATCH 07/16] Dev --- Dockerfile | 2 +- docker-compose.yaml | 3 +-- .../ru/soune/nocopy/service/YandexSearchService.java | 9 ++++----- src/main/resources/application.yaml | 5 +++++ 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9fda8a5..03d3325 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ RUN mkdir -p /data/uploads && chmod 755 /data/uploads EXPOSE 8080 -ENTRYPOINT ["sh", "-c", "exec java ${JAVA_OPTS} -jar app.jar"] +CMD ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 7ee937f..0ef8c6b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -67,8 +67,7 @@ services: POSTGRES_HOST: db STORAGE_SERVICE_URL: http://storage:8081 SPRING_PROFILES_ACTIVE: docker - - JAVA_OPTS: "-DYANDEX_API_KEY=AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q -DYANDEX_FOLDER_ID=b1gokpdbm6qfpsou8pcd -DYANDEX_SEARCH_URL='https://searchapi.api.cloud.yandex.net/v2/image/search_by_image'" + SPRING_APPLICATION_JSON: '{"yandex":{"api-key":"AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q","folder-id":"b1gokpdbm6qfpsou8pcd","search-url":"https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"}}' depends_on: - db diff --git a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java index 66f3f18..257fd90 100644 --- a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java +++ b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java @@ -28,19 +28,18 @@ import java.util.Map; @Service @RequiredArgsConstructor public class YandexSearchService { - private final FileEntityRepository fileEntityRepository; + private final ObjectMapper objectMapper; - @Value("${YANDEX_API_KEY}") + @Value("${yandex.api-key}") private String apiKey; - @Value("${YANDEX_FOLDER_ID}") + @Value("${yandex.folder-id}") private String folderId; - @Value("${YANDEX_SEARCH_URL}") + @Value("${yandex.search-url}") private String searchUrl; - @PostConstruct public void init() { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index da03a23..77d6ac6 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -40,6 +40,11 @@ security: allowed-headers: "*" allow-credentials: true +yandex: + api-key: ${yandex.api-key} + folder-id: ${yandex.folder-id} + search-url: ${yandex.search-url} + server: port: ${SERVER_PORT:8080} From 7b8546fd4d4fda71407b5426d6b1e5922ac0cdd9 Mon Sep 17 00:00:00 2001 From: vladp Date: Sat, 10 Jan 2026 01:06:32 +0700 Subject: [PATCH 08/16] Dev --- docker-compose.yaml | 5 +++-- .../nocopy/handler/ImageFoundRequestHandler.java | 1 - .../ru/soune/nocopy/service/YandexSearchService.java | 1 + src/main/resources/application.yaml | 12 ++++++------ 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 0ef8c6b..192b2d6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -67,8 +67,9 @@ services: POSTGRES_HOST: db STORAGE_SERVICE_URL: http://storage:8081 SPRING_PROFILES_ACTIVE: docker - SPRING_APPLICATION_JSON: '{"yandex":{"api-key":"AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q","folder-id":"b1gokpdbm6qfpsou8pcd","search-url":"https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"}}' - + YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q + YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd + YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image" depends_on: - db ports: diff --git a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java index dfe67cc..3d13d4a 100644 --- a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java @@ -19,7 +19,6 @@ public class ImageFoundRequestHandler implements RequestHandler { private final ObjectMapper objectMapper; - @Autowired private final YandexSearchService yandexSearchService; @Override diff --git a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java index 257fd90..d0f4710 100644 --- a/src/main/java/ru/soune/nocopy/service/YandexSearchService.java +++ b/src/main/java/ru/soune/nocopy/service/YandexSearchService.java @@ -40,6 +40,7 @@ public class YandexSearchService { @Value("${yandex.search-url}") private String searchUrl; + @PostConstruct public void init() { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 77d6ac6..35e3fc8 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -40,11 +40,6 @@ security: allowed-headers: "*" allow-credentials: true -yandex: - api-key: ${yandex.api-key} - folder-id: ${yandex.folder-id} - search-url: ${yandex.search-url} - server: port: ${SERVER_PORT:8080} @@ -56,4 +51,9 @@ logging: file: name: logs/application.log pattern: - console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" \ No newline at end of file + console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" + +yandex: + api-key: ${YANDEX_API_KEY} + folder-id: ${YANDEX_FOLDER_ID} + search-url: ${YANDEX_SEARCH_URL} \ No newline at end of file From 344ec075d45526475a3d284c0dcfad57a4d6b9ec Mon Sep 17 00:00:00 2001 From: vladp Date: Sat, 10 Jan 2026 01:19:30 +0700 Subject: [PATCH 09/16] Dev --- src/main/resources/application.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 35e3fc8..9a34782 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -54,6 +54,6 @@ logging: console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" yandex: - api-key: ${YANDEX_API_KEY} - folder-id: ${YANDEX_FOLDER_ID} - search-url: ${YANDEX_SEARCH_URL} \ No newline at end of file + api-key: ${YANDEX_API_KEY:AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q} + folder-id: ${YANDEX_FOLDER_ID:b1gokpdbm6qfpsou8pcd} + search-url: ${YANDEX_SEARCH_URL:https://searchapi.api.cloud.yandex.net/v2/image/search_by_image} \ No newline at end of file From 5c6309e2289414495be66b5e13cdd2527bd0b3f5 Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 14 Jan 2026 18:47:34 +0700 Subject: [PATCH 10/16] NCBACK-33 fix check company name --- .../handler/validator/RegRequestValidator.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/main/java/ru/soune/nocopy/handler/validator/RegRequestValidator.java b/src/main/java/ru/soune/nocopy/handler/validator/RegRequestValidator.java index f3dbda7..19d4c77 100644 --- a/src/main/java/ru/soune/nocopy/handler/validator/RegRequestValidator.java +++ b/src/main/java/ru/soune/nocopy/handler/validator/RegRequestValidator.java @@ -13,7 +13,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}$"; @@ -74,21 +74,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 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) { From d3e50aa8d151caace253ad0d8256e9b2fc017777 Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 14 Jan 2026 19:10:22 +0700 Subject: [PATCH 11/16] NCBACK-19 add all case for json --- src/main/java/ru/soune/nocopy/dto/BaseRequest.java | 2 ++ src/main/java/ru/soune/nocopy/dto/BaseResponse.java | 2 ++ src/main/java/ru/soune/nocopy/dto/LoginRequest.java | 2 ++ src/main/java/ru/soune/nocopy/dto/RegRequest.java | 2 ++ src/main/java/ru/soune/nocopy/dto/UserRequest.java | 1 + 5 files changed, 9 insertions(+) diff --git a/src/main/java/ru/soune/nocopy/dto/BaseRequest.java b/src/main/java/ru/soune/nocopy/dto/BaseRequest.java index b02ad8c..684e79a 100644 --- a/src/main/java/ru/soune/nocopy/dto/BaseRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/BaseRequest.java @@ -1,9 +1,11 @@ package ru.soune.nocopy.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data +@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) public class BaseRequest { @JsonProperty("version") Integer version; diff --git a/src/main/java/ru/soune/nocopy/dto/BaseResponse.java b/src/main/java/ru/soune/nocopy/dto/BaseResponse.java index b60419a..75f1543 100644 --- a/src/main/java/ru/soune/nocopy/dto/BaseResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/BaseResponse.java @@ -1,11 +1,13 @@ package ru.soune.nocopy.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor +@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) public class BaseResponse { @JsonProperty("msg_id") private Integer msgId; diff --git a/src/main/java/ru/soune/nocopy/dto/LoginRequest.java b/src/main/java/ru/soune/nocopy/dto/LoginRequest.java index d64fa8b..b0548bc 100644 --- a/src/main/java/ru/soune/nocopy/dto/LoginRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/LoginRequest.java @@ -1,5 +1,6 @@ package ru.soune.nocopy.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @@ -10,6 +11,7 @@ import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor +@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) public class LoginRequest { @NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128) diff --git a/src/main/java/ru/soune/nocopy/dto/RegRequest.java b/src/main/java/ru/soune/nocopy/dto/RegRequest.java index e08f404..d7adfea 100644 --- a/src/main/java/ru/soune/nocopy/dto/RegRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/RegRequest.java @@ -1,10 +1,12 @@ package ru.soune.nocopy.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.*; import lombok.Data; @Data +@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) public class RegRequest { @NotEmpty(message = "Full name is required") private String fullName; diff --git a/src/main/java/ru/soune/nocopy/dto/UserRequest.java b/src/main/java/ru/soune/nocopy/dto/UserRequest.java index c5f1662..91f7379 100644 --- a/src/main/java/ru/soune/nocopy/dto/UserRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/UserRequest.java @@ -10,6 +10,7 @@ import ru.soune.nocopy.entity.GenderType; import java.time.LocalDate; @Data +@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) public class UserRequest { @Size(min = 2) private String fullName; From 82a57a2983c5b8d0b7c1f26bab5c4a7528c53cf0 Mon Sep 17 00:00:00 2001 From: vladp Date: Thu, 15 Jan 2026 11:09:05 +0700 Subject: [PATCH 12/16] NCBACK-32 add all case for json --- .../ru/soune/nocopy/handler/validator/RegRequestValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/soune/nocopy/handler/validator/RegRequestValidator.java b/src/main/java/ru/soune/nocopy/handler/validator/RegRequestValidator.java index 19d4c77..e01df8e 100644 --- a/src/main/java/ru/soune/nocopy/handler/validator/RegRequestValidator.java +++ b/src/main/java/ru/soune/nocopy/handler/validator/RegRequestValidator.java @@ -13,7 +13,7 @@ import java.util.*; @Component public class RegRequestValidator implements Validator { - private static final String COMPANY_REGEX = "^[a-zA-Zа-яА-ЯёЁ0-9\\s\\-&.,'()\"\"]{1,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}$"; From 1a6823f7a916ebbc9321942c3f688b3cb947e434 Mon Sep 17 00:00:00 2001 From: vladp Date: Thu, 15 Jan 2026 16:17:03 +0700 Subject: [PATCH 13/16] DEV_DB change db creds --- .env | 6 +- docker-compose.yaml | 19 ++-- infrastructure/jenkins/deploy.groovy | 129 ++++++++++++++------------- init-scripts/init-user.sql | 19 ++++ 4 files changed, 102 insertions(+), 71 deletions(-) create mode 100644 init-scripts/init-user.sql diff --git a/.env b/.env index d3e4347..e54b26f 100644 --- a/.env +++ b/.env @@ -1,10 +1,10 @@ POSTGRES_DB=no_copy_ -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres +POSTGRES_USER=ncp_db +POSTGRES_PASSWORD=ncpDbApp POSTGRES_PORT=5432 POSTGRES_HOST=postgres REDIS_HOST=redis REDIS_PORT=6379 -SERVER_PORT=8080 +SERVER_PORT=8080 \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 192b2d6..6a69921 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,7 @@ services: restart: unless-stopped db: - image: postgres:17 + image: postgres:17.7 restart: always deploy: resources: @@ -30,14 +30,17 @@ services: memory: 1G environment: POSTGRES_DB: no_copy_ - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres +# POSTGRES_USER: postgres +# POSTGRES_PASSWORD: postgres + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_SHARED_BUFFERS: 512MB POSTGRES_EFFECTIVE_CACHE_SIZE: 1536MB ports: - "54320:5432" volumes: - pgdata:/var/lib/postgresql/data + - ./init-scripts:/docker-entrypoint-initdb.d:ro container_name: postgres networks: app-network: @@ -61,8 +64,10 @@ services: # FILE_CHUNK_SIZE: 1048576 FILE_CHUNK_SIZE: 1000000 POSTGRES_DB: no_copy_ - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres +# POSTGRES_USER: postgres +# POSTGRES_PASSWORD: postgres + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PORT: 5432 POSTGRES_HOST: db STORAGE_SERVICE_URL: http://storage:8081 @@ -82,6 +87,10 @@ services: - api volumes: - uploads_data:/data/uploads:rw + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d no_copy_" ] + interval: 10s + retries: 5 grafana: image: grafana/grafana:10.3.1 diff --git a/infrastructure/jenkins/deploy.groovy b/infrastructure/jenkins/deploy.groovy index 7611efe..b57f515 100644 --- a/infrastructure/jenkins/deploy.groovy +++ b/infrastructure/jenkins/deploy.groovy @@ -30,6 +30,7 @@ pipeline { } } + stage('Deploy with docker-compose') { steps { script { @@ -38,71 +39,73 @@ pipeline { credentialsId: 'server-root-password', usernameVariable: 'SSH_USER', passwordVariable: 'SSH_PASS' - ) + ), + string(credentialsId: 'db-user', variable: 'DB_USER'), + string(credentialsId: 'db-password', variable: 'DB_PASSWORD') ]) { sh """ - echo "Deploying branch: ${params.BRANCH}" - - echo "Copying files to server..." - sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "mkdir -p /opt/deployments/${params.BRANCH}" - sshpass -p '$SSH_PASS' scp -r -o StrictHostKeyChecking=no ./* $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/ - - sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER " - cd /opt/deployments/${params.BRANCH} - - echo '1. Остановка старого приложения...' - docker stop app-backend 2>/dev/null || echo 'Контейнер не найден' - docker rm app-backend 2>/dev/null || echo 'Контейнер не найден' - - echo '2. Удаление старых образов...' - docker images --filter 'reference=*app*' -q | xargs -r docker rmi -f 2>/dev/null || echo 'Нет образов для удаления' - - echo '3. Создание сети если нужно...' - docker network create app-network 2>/dev/null || echo 'Сеть уже существует' - - echo '4. Запуск инфраструктуры...' - docker-compose up -d db storage - - echo '5. Ожидание PostgreSQL...' - sleep 10 - - echo '6. Сборка нового образа приложения...' - docker build --no-cache -t app-backend:latest . - - echo '7. Запуск приложения...' - docker run -d \\ - --name app-backend \\ - --network app-network \\ - --network-alias app \\ - -p 80:8080 \\ - -v uploads_data:/data/uploads:rw \\ - -e POSTGRES_DB=no_copy_ \\ - -e POSTGRES_USER=postgres \\ - -e POSTGRES_PASSWORD=postgres \\ - -e POSTGRES_PORT=5432 \\ - -e POSTGRES_HOST=db \\ - --restart unless-stopped \\ - app-backend:latest - - echo '8. Запуск мониторинга...' - docker-compose up -d grafana prometheus loki tempo alloy - - echo '9. Проверка...' - sleep 5 - - echo 'Статус контейнеров:' - docker ps --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}' - - echo '10. Проверка health...' - if curl -s -f http://localhost:80/health > /dev/null 2>&1; then - echo 'Приложение работает' - echo 'URL: http://${params.SERVER}:80' - else - echo 'Проверка health не удалась' - docker logs app-backend --tail=20 - fi - " - """ + echo "Deploying branch: ${params.BRANCH}" + + echo "Copying files to server..." + sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "mkdir -p /opt/deployments/${params.BRANCH}" + sshpass -p '$SSH_PASS' scp -r -o StrictHostKeyChecking=no ./* $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/ + + sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER " + cd /opt/deployments/${params.BRANCH} + + echo '1. Остановка старого приложения...' + docker stop app-backend 2>/dev/null || echo 'Контейнер не найден' + docker rm app-backend 2>/dev/null || echo 'Контейнер не найден' + + echo '2. Удаление старых образов...' + docker images --filter 'reference=*app*' -q | xargs -r docker rmi -f 2>/dev/null || echo 'Нет образов для удаления' + + echo '3. Создание сети если нужно...' + docker network create app-network 2>/dev/null || echo 'Сеть уже существует' + + echo '4. Запуск инфраструктуры...' + docker-compose up -d db storage + + echo '5. Ожидание PostgreSQL...' + sleep 10 + + echo '6. Сборка нового образа приложения...' + docker build --no-cache -t app-backend:latest . + + echo '7. Запуск приложения...' + docker run -d \\ + --name app-backend \\ + --network app-network \\ + --network-alias app \\ + -p 80:8080 \\ + -v uploads_data:/data/uploads:rw \\ + -e POSTGRES_DB=no_copy_ \\ + -e POSTGRES_USER=$DB_USER \\ + -e POSTGRES_PASSWORD=$DB_PASSWORD \\ + -e POSTGRES_PORT=5432 \\ + -e POSTGRES_HOST=db \\ + --restart unless-stopped \\ + app-backend:latest + + echo '8. Запуск мониторинга...' + docker-compose up -d grafana prometheus loki tempo alloy + + echo '9. Проверка...' + sleep 5 + + echo 'Статус контейнеров:' + docker ps --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}' + + echo '10. Проверка health...' + if curl -s -f http://localhost:80/health > /dev/null 2>&1; then + echo 'Приложение работает' + echo 'URL: http://${params.SERVER}:80' + else + echo 'Проверка health не удалась' + docker logs app-backend --tail=20 + fi + " + """ } } } diff --git a/init-scripts/init-user.sql b/init-scripts/init-user.sql new file mode 100644 index 0000000..f1d560e --- /dev/null +++ b/init-scripts/init-user.sql @@ -0,0 +1,19 @@ +DO +$do$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'ncp_db') THEN + CREATE USER ncp_db WITH PASSWORD 'ncpDbApp'; +END IF; +END +$do$; + +DO +$do$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_database WHERE datname = 'no_copy_') THEN + CREATE DATABASE no_copy_ OWNER ncp_db; +END IF; +END +$do$; + +GRANT ALL PRIVILEGES ON DATABASE no_copy_ TO ncp_db; \ No newline at end of file From de810e32b473dd55acddf63a71d223b512b90f81 Mon Sep 17 00:00:00 2001 From: vladp Date: Thu, 15 Jan 2026 16:42:37 +0700 Subject: [PATCH 14/16] DEV_DB change db creds --- README.md | 48 ++++++++++++++++++++++++++++ infrastructure/jenkins/deploy.groovy | 4 +-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e69de29..72cd929 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,48 @@ +суперпользователь БД: +postgres/postgres +пользователь бд для бэка +ncp_db / ncpDbApp + +Подключение к БД для приложения,если volume не существует : + +Создаться автоматически. + +Если уже существует volume : + +docker exec -it postgres psql -U postgres -d postgres + +CREATE USER ncp_db WITH PASSWORD 'ncpDbApp'; + +GRANT ALL PRIVILEGES ON DATABASE no_copy_ TO ncp_db; + +\du + +\q + +------- + +psql -U postgres -d no_copy_ + +-- база + +GRANT CONNECT ON DATABASE no_copy_ TO ncp_db; + +-- схема + +GRANT USAGE ON SCHEMA public TO ncp_db; + +-- существующие таблицы + +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ncp_db; + +-- существующие sequence (очень важно для id) + +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ncp_db; + +-- будущие таблицы + +ALTER DEFAULT PRIVILEGES IN SCHEMA public +GRANT ALL ON TABLES TO ncp_db; + +ALTER DEFAULT PRIVILEGES IN SCHEMA public +GRANT ALL ON SEQUENCES TO ncp_db; diff --git a/infrastructure/jenkins/deploy.groovy b/infrastructure/jenkins/deploy.groovy index b57f515..dc09c2e 100644 --- a/infrastructure/jenkins/deploy.groovy +++ b/infrastructure/jenkins/deploy.groovy @@ -40,8 +40,8 @@ pipeline { usernameVariable: 'SSH_USER', passwordVariable: 'SSH_PASS' ), - string(credentialsId: 'db-user', variable: 'DB_USER'), - string(credentialsId: 'db-password', variable: 'DB_PASSWORD') + string(credentialsId: 'DB_USER', variable: 'DB_USER'), + string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD') ]) { sh """ echo "Deploying branch: ${params.BRANCH}" From 0ed3f958ffeb3a4469c13b9466ddd857390b063a Mon Sep 17 00:00:00 2001 From: vladp Date: Thu, 15 Jan 2026 16:52:33 +0700 Subject: [PATCH 15/16] DEV_DB change db creds --- init-scripts/01-init.sql | 19 +++++++++++++++++++ init-scripts/init-user.sql | 19 ------------------- 2 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 init-scripts/01-init.sql delete mode 100644 init-scripts/init-user.sql diff --git a/init-scripts/01-init.sql b/init-scripts/01-init.sql new file mode 100644 index 0000000..60bedbc --- /dev/null +++ b/init-scripts/01-init.sql @@ -0,0 +1,19 @@ +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'ncp_db') THEN + CREATE USER ncp_db WITH PASSWORD 'ncpDbApp'; +END IF; +END +$$; + +GRANT CONNECT ON DATABASE no_copy_ TO ncp_db; +GRANT USAGE ON SCHEMA public TO ncp_db; + +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ncp_db; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ncp_db; + +ALTER DEFAULT PRIVILEGES IN SCHEMA public +GRANT ALL ON TABLES TO ncp_db; + +ALTER DEFAULT PRIVILEGES IN SCHEMA public +GRANT ALL ON SEQUENCES TO ncp_db; \ No newline at end of file diff --git a/init-scripts/init-user.sql b/init-scripts/init-user.sql deleted file mode 100644 index f1d560e..0000000 --- a/init-scripts/init-user.sql +++ /dev/null @@ -1,19 +0,0 @@ -DO -$do$ -BEGIN - IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'ncp_db') THEN - CREATE USER ncp_db WITH PASSWORD 'ncpDbApp'; -END IF; -END -$do$; - -DO -$do$ -BEGIN - IF NOT EXISTS (SELECT FROM pg_database WHERE datname = 'no_copy_') THEN - CREATE DATABASE no_copy_ OWNER ncp_db; -END IF; -END -$do$; - -GRANT ALL PRIVILEGES ON DATABASE no_copy_ TO ncp_db; \ No newline at end of file From 50279201998e19d60175c65051968c766bd1f4a2 Mon Sep 17 00:00:00 2001 From: vladp Date: Fri, 16 Jan 2026 23:59:07 +0700 Subject: [PATCH 16/16] dev info for docker user --- README.md | 20 ++++++++++++++++++++ docker-compose.yaml | 5 ----- src/main/resources/application.yaml | 2 -- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 72cd929..d133197 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ +Создаем сеть: +docker network create app-network + +Войти в БД: +docker exec -it postgres psql -U ncp_db -d no_copy_ + +Выполнить команду: +\du + +Должен быть один пользователь: + List of roles +Role name | Attributes +-----------+------------------------------------------------------------ +ncp_db | Superuser, Create role, Create DB, Replication, Bypass RLS + суперпользователь БД: postgres/postgres пользователь бд для бэка @@ -46,3 +61,8 @@ GRANT ALL ON TABLES TO ncp_db; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO ncp_db; + + +Зайти в контейнер: +docker exec -it {name} bash + diff --git a/docker-compose.yaml b/docker-compose.yaml index 6a69921..bb1e1b7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -30,8 +30,6 @@ services: memory: 1G environment: POSTGRES_DB: no_copy_ -# POSTGRES_USER: postgres -# POSTGRES_PASSWORD: postgres POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_SHARED_BUFFERS: 512MB @@ -61,11 +59,8 @@ services: environment: FILE_STORAGE_PATH: /data/uploads MAX_FILE_SIZE: 10737418240 -# FILE_CHUNK_SIZE: 1048576 FILE_CHUNK_SIZE: 1000000 POSTGRES_DB: no_copy_ -# POSTGRES_USER: postgres -# POSTGRES_PASSWORD: postgres POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PORT: 5432 diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 9a34782..db4e38c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -24,8 +24,6 @@ spring: file: storage: base-path: ${FILE_STORAGE_PATH:/data/uploads} - # chunk-size: ${FILE_CHUNK_SIZE:5242880} # 5MB -# chunk-size: ${FILE_CHUNK_SIZE:1048576} # 1MB chunk-size: ${FILE_CHUNK_SIZE:1000000} # 1MB max-file-size: ${MAX_FILE_SIZE:10737418240} # 10GB max-retry-attempts: ${MAX_RETRY_ATTEMPTS:3}