From 413b1293a824bea03a78f56da0127e8ee92f6b7b Mon Sep 17 00:00:00 2001 From: bivashy Date: Sun, 7 Sep 2025 01:05:21 +0500 Subject: [PATCH] Initial implementation of BFF, search-api integration and eureka --- .gitignore | 2 + Dockerfile.prod | 12 +- anyame-kodik-search-api/pom.xml | 167 ++++++++ .../src/main/resources/openapi.json | 365 ++++++++++++++++++ main-app/pom.xml | 112 ++++++ .../bff/service}/AnyameVueBffApplication.java | 2 +- .../config/GlobalExceptionHandler.java | 107 +++++ .../config/SearchApiClientConfiguration.java | 90 +++++ .../service/controller/TestController.java | 24 ++ .../ServiceUnavailableException.java | 7 + .../main/resources/application.properties | 0 .../AnyameVueBffApplicationTests.java | 8 +- pom.xml | 103 +---- 13 files changed, 905 insertions(+), 94 deletions(-) create mode 100644 anyame-kodik-search-api/pom.xml create mode 100644 anyame-kodik-search-api/src/main/resources/openapi.json create mode 100644 main-app/pom.xml rename {src/main/java/com/backend/extractor/kodik/service/anyame/bff => main-app/src/main/java/com/backend/vue/bff/service}/AnyameVueBffApplication.java (89%) create mode 100644 main-app/src/main/java/com/backend/vue/bff/service/config/GlobalExceptionHandler.java create mode 100644 main-app/src/main/java/com/backend/vue/bff/service/config/SearchApiClientConfiguration.java create mode 100644 main-app/src/main/java/com/backend/vue/bff/service/controller/TestController.java create mode 100644 main-app/src/main/java/com/backend/vue/bff/service/exception/ServiceUnavailableException.java rename {src => main-app/src}/main/resources/application.properties (100%) rename {src/test/java/com/backend/extractor/kodik/service/anyame/bff => main-app/src/test/java/com/backend/vue/bff/service}/AnyameVueBffApplicationTests.java (63%) diff --git a/.gitignore b/.gitignore index 667aaef..641b513 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ target/ .mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +.mvn +/bin/ ### STS ### .apt_generated diff --git a/Dockerfile.prod b/Dockerfile.prod index e0f53b9..9234b09 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -2,16 +2,20 @@ FROM maven:3.9.6-eclipse-temurin-21 AS builder WORKDIR /workspace COPY pom.xml . -RUN mvn dependency:go-offline -B -COPY src ./src -RUN mvn clean package -DskipTests +COPY main-app/pom.xml ./main-app/ +COPY anyame-kodik-search-api/pom.xml ./anyame-kodik-search-api/ +RUN --mount=type=cache,target=/root/.m2 mvn dependency:go-offline -B +COPY main-app/src ./main-app/src +COPY anyame-kodik-search-api/src ./anyame-kodik-search-api/src + +RUN --mount=type=cache,target=/root/.m2 mvn clean package -DskipTests # Create optimized runtime FROM eclipse-temurin:21 AS app-build ENV RELEASE=21 WORKDIR /opt/build -COPY --from=builder /workspace/target/*.jar ./application.jar +COPY --from=builder /workspace/main-app/target/*.jar ./application.jar RUN java -Djarmode=layertools -jar application.jar extract RUN $JAVA_HOME/bin/jlink \ diff --git a/anyame-kodik-search-api/pom.xml b/anyame-kodik-search-api/pom.xml new file mode 100644 index 0000000..c680ea0 --- /dev/null +++ b/anyame-kodik-search-api/pom.xml @@ -0,0 +1,167 @@ + + + 4.0.0 + + anyame-bff-parent + com.backend.vue.bff.service + 0.0.1-SNAPSHOT + + + com.backend.vue.bff.service + anyame-kodik-search-api + 0.0.1-SNAPSHOT + anyame-kodik-search-api + Search API for anyame-kodik + + + + + + + + + + + + + + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + 1.6.3 + 2.17.1 + 2.17.1 + 4.0.0 + 2.11.0 + 2.1.1 + 3.0.2 + 1.0.1 + 5.10.3 + 1.3.2 + 3.1.1 + 3.0.2 + + + + io.swagger + swagger-annotations + ${swagger-annotations-version} + + + + com.google.code.findbugs + jsr305 + ${jsr305.version} + + + com.squareup.retrofit2 + retrofit + ${retrofit-version} + + + com.squareup.retrofit2 + converter-scalars + ${retrofit-version} + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.client + ${oltu-version} + + + org.apache.oltu.oauth2 + common + + + + + + com.squareup.retrofit2 + converter-jackson + ${retrofit-version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-databind-version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + + jakarta.ws.rs + jakarta.ws.rs-api + ${jakarta.ws.rs-api-version} + + + jakarta.annotation + jakarta.annotation-api + ${jakarta-annotation-version} + provided + + + javax.annotation + javax.annotation-api + ${javax-annotation-api.version} + + + + jakarta.validation + jakarta.validation-api + ${jakarta-validation-api.version} + + + + org.junit.jupiter + junit-jupiter-api + ${junit-version} + test + + + + + + + org.openapitools + openapi-generator-maven-plugin + 7.14.0 + + + + generate + + + ${project.basedir}/src/main/resources/openapi.json + java + + retrofit2 + com.backend.search.kodik.api + com.backend.search.kodik.model + false + true + jackson + + + + + + + + diff --git a/anyame-kodik-search-api/src/main/resources/openapi.json b/anyame-kodik-search-api/src/main/resources/openapi.json new file mode 100644 index 0000000..d956a9a --- /dev/null +++ b/anyame-kodik-search-api/src/main/resources/openapi.json @@ -0,0 +1,365 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "Generated server url" + } + ], + "paths": { + "/search": { + "get": { + "tags": [ + "search-controller" + ], + "operationId": "search", + "parameters": [ + { + "name": "title", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/KodikResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "KodikResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Result" + } + } + } + }, + "MaterialData": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "year": { + "type": "integer", + "format": "int32" + }, + "description": { + "type": "string" + }, + "screenshots": { + "type": "array", + "items": { + "type": "string" + } + }, + "duration": { + "type": "integer", + "format": "int32" + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "genres": { + "type": "array", + "items": { + "type": "string" + } + }, + "actors": { + "type": "array", + "items": { + "type": "string" + } + }, + "directors": { + "type": "array", + "items": { + "type": "string" + } + }, + "producers": { + "type": "array", + "items": { + "type": "string" + } + }, + "writers": { + "type": "array", + "items": { + "type": "string" + } + }, + "composers": { + "type": "array", + "items": { + "type": "string" + } + }, + "editors": { + "type": "array", + "items": { + "type": "string" + } + }, + "designers": { + "type": "array", + "items": { + "type": "string" + } + }, + "operators": { + "type": "array", + "items": { + "type": "string" + } + }, + "anime_title": { + "type": "string" + }, + "title_en": { + "type": "string" + }, + "other_titles": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_titles_en": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_titles_jp": { + "type": "array", + "items": { + "type": "string" + } + }, + "anime_license_name": { + "type": "string" + }, + "anime_licensed_by": { + "type": "array", + "items": { + "type": "string" + } + }, + "anime_kind": { + "type": "string" + }, + "all_status": { + "type": "string" + }, + "anime_status": { + "type": "string" + }, + "anime_description": { + "type": "string" + }, + "poster_url": { + "type": "string" + }, + "anime_poster_url": { + "type": "string" + }, + "all_genres": { + "type": "array", + "items": { + "type": "string" + } + }, + "anime_genres": { + "type": "array", + "items": { + "type": "string" + } + }, + "anime_studios": { + "type": "array", + "items": { + "type": "string" + } + }, + "kinopoisk_rating": { + "type": "number", + "format": "double" + }, + "kinopoisk_votes": { + "type": "integer", + "format": "int32" + }, + "imdb_rating": { + "type": "number", + "format": "double" + }, + "imdb_votes": { + "type": "integer", + "format": "int32" + }, + "shikimori_rating": { + "type": "number", + "format": "double" + }, + "shikimori_votes": { + "type": "integer", + "format": "int32" + }, + "premiere_world": { + "type": "string" + }, + "aired_at": { + "type": "string" + }, + "released_at": { + "type": "string" + }, + "rating_mpaa": { + "type": "string" + }, + "minimal_age": { + "type": "integer", + "format": "int32" + }, + "episodes_total": { + "type": "integer", + "format": "int32" + }, + "episodes_aired": { + "type": "integer", + "format": "int32" + } + } + }, + "Result": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "link": { + "type": "string" + }, + "title": { + "type": "string" + }, + "translation": { + "$ref": "#/components/schemas/Translation" + }, + "year": { + "type": "integer", + "format": "int32" + }, + "quality": { + "type": "string" + }, + "camrip": { + "type": "boolean" + }, + "lgbt": { + "type": "boolean" + }, + "screenshots": { + "type": "array", + "items": { + "type": "string" + } + }, + "title_orig": { + "type": "string" + }, + "other_title": { + "type": "string" + }, + "last_season": { + "type": "integer", + "format": "int32" + }, + "last_episode": { + "type": "integer", + "format": "int32" + }, + "episodes_count": { + "type": "integer", + "format": "int32" + }, + "kinopoisk_id": { + "type": "string" + }, + "imdb_id": { + "type": "string" + }, + "worldart_link": { + "type": "string" + }, + "shikimori_id": { + "type": "string" + }, + "blocked_countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "material_data": { + "$ref": "#/components/schemas/MaterialData" + } + } + }, + "Translation": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + } + } +} diff --git a/main-app/pom.xml b/main-app/pom.xml new file mode 100644 index 0000000..85cf8bd --- /dev/null +++ b/main-app/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.5 + + + com.backend.vue.bff.service + anyame-vue-bff + 0.0.1-SNAPSHOT + anyame-vue-bff + BFF for Vue frontend + + + + + + + + + + + + + + + 21 + + 3.0.0 + 4.0.0 + 2.8.9 + 2025.0.0 + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-server + + + + com.squareup.retrofit2 + converter-jackson + ${retrofit.version} + + + me.paulschwarz + spring-dotenv + ${spring-dotenv.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc-openapi-starter.version} + + + com.backend.vue.bff.service + anyame-kodik-search-api + ${project.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/com/backend/extractor/kodik/service/anyame/bff/AnyameVueBffApplication.java b/main-app/src/main/java/com/backend/vue/bff/service/AnyameVueBffApplication.java similarity index 89% rename from src/main/java/com/backend/extractor/kodik/service/anyame/bff/AnyameVueBffApplication.java rename to main-app/src/main/java/com/backend/vue/bff/service/AnyameVueBffApplication.java index c343950..0384cc2 100644 --- a/src/main/java/com/backend/extractor/kodik/service/anyame/bff/AnyameVueBffApplication.java +++ b/main-app/src/main/java/com/backend/vue/bff/service/AnyameVueBffApplication.java @@ -1,4 +1,4 @@ -package com.backend.extractor.kodik.service.anyame.bff; +package com.backend.vue.bff.service; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/main-app/src/main/java/com/backend/vue/bff/service/config/GlobalExceptionHandler.java b/main-app/src/main/java/com/backend/vue/bff/service/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..bd1abdf --- /dev/null +++ b/main-app/src/main/java/com/backend/vue/bff/service/config/GlobalExceptionHandler.java @@ -0,0 +1,107 @@ +package com.backend.vue.bff.service.config; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.backend.vue.bff.service.exception.ServiceUnavailableException; + +@RestControllerAdvice +public class GlobalExceptionHandler { + public static final Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler"); + + @ExceptionHandler({ UnsatisfiedDependencyException.class, BeanCreationException.class, + ServiceUnavailableException.class }) + public ResponseEntity handleServiceUnavailable(ServiceUnavailableException e) { + ErrorResponse error = new ErrorResponse( + "SERVICE_UNAVAILABLE", + e.getMessage(), + HttpStatus.SERVICE_UNAVAILABLE.value()); + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(error); + } + + @ExceptionHandler(IOException.class) + public ResponseEntity handleIOException(IOException e) { + ErrorResponse error = new ErrorResponse( + "API_CALL_FAILED", + "Failed to communicate with external service: " + e.getMessage(), + HttpStatus.INTERNAL_SERVER_ERROR.value()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception e) { + logger.error("Exception type: {}", e.getClass().getSimpleName()); + logger.error("Exception message: {}", e.getMessage()); + + Throwable cause = e.getCause(); + while (cause != null) { + logger.error("Cause type: {}", cause.getClass().getSimpleName()); + if (cause instanceof ServiceUnavailableException) { + return handleServiceUnavailable((ServiceUnavailableException) cause); + } + cause = cause.getCause(); + } + + ErrorResponse error = new ErrorResponse( + "INTERNAL_ERROR", + "An unexpected error occurred", + HttpStatus.INTERNAL_SERVER_ERROR.value()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + + public static class ErrorResponse { + private String code; + private String message; + private int status; + private long timestamp; + + public ErrorResponse(String code, String message, int status) { + this.code = code; + this.message = message; + this.status = status; + this.timestamp = System.currentTimeMillis(); + } + + // Getters and setters + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + } + +} diff --git a/main-app/src/main/java/com/backend/vue/bff/service/config/SearchApiClientConfiguration.java b/main-app/src/main/java/com/backend/vue/bff/service/config/SearchApiClientConfiguration.java new file mode 100644 index 0000000..e85c329 --- /dev/null +++ b/main-app/src/main/java/com/backend/vue/bff/service/config/SearchApiClientConfiguration.java @@ -0,0 +1,90 @@ +package com.backend.vue.bff.service.config; + +import java.util.List; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +import com.backend.search.kodik.ApiClient; +import com.backend.search.kodik.JSON; +import com.backend.search.kodik.api.SearchControllerApi; +import com.backend.vue.bff.service.exception.ServiceUnavailableException; + +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; +import retrofit2.converter.scalars.ScalarsConverterFactory; + +@Configuration +@EnableDiscoveryClient +public class SearchApiClientConfiguration { + + private final DiscoveryClient discoveryClient; + + public SearchApiClientConfiguration(DiscoveryClient discoveryClient) { + this.discoveryClient = discoveryClient; + } + + @Bean + @Lazy + public FactoryBean apiClientFactory() { + return new ApiClientFactoryBean(); + } + + @Bean + @Lazy + public SearchControllerApi searchApi(ApiClient apiClient) { + return apiClient.createService(SearchControllerApi.class); + } + + private class ApiClientFactoryBean implements FactoryBean { + @Override + public ApiClient getObject() throws Exception { + ApiClient client = new ApiClient(); + + String baseUrl = resolveServiceUrl("ANYAME-KODIK-SEARCH-BACKEND"); + + JSON json = new JSON(); + + Retrofit.Builder adapterBuilder = new Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create(json.getMapper())); + + client.setAdapterBuilder(adapterBuilder); + + return client; + } + + @Override + public Class getObjectType() { + return ApiClient.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + private String resolveServiceUrl(String serviceName) { + List instances = discoveryClient.getInstances(serviceName); + + if (instances.isEmpty()) { + throw new ServiceUnavailableException(); + } + + ServiceInstance instance = instances.get(0); + String baseUrl = instance.getUri().toString(); + + if (!baseUrl.endsWith("/")) { + baseUrl = baseUrl + "/"; + } + + return baseUrl; + } + } +} diff --git a/main-app/src/main/java/com/backend/vue/bff/service/controller/TestController.java b/main-app/src/main/java/com/backend/vue/bff/service/controller/TestController.java new file mode 100644 index 0000000..55030ae --- /dev/null +++ b/main-app/src/main/java/com/backend/vue/bff/service/controller/TestController.java @@ -0,0 +1,24 @@ +package com.backend.vue.bff.service.controller; + +import java.io.IOException; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.backend.search.kodik.api.SearchControllerApi; + +@RestController +public class TestController { + private final ObjectProvider controllerApiFactory; + + public TestController(ObjectProvider controllerApiFactory) { + this.controllerApiFactory = controllerApiFactory; + } + + @GetMapping("/test") + public String test() throws IOException { + SearchControllerApi api = controllerApiFactory.getObject(); + return Integer.toString(api.search("Шарлотта").execute().body().getResults().size()); + } +} diff --git a/main-app/src/main/java/com/backend/vue/bff/service/exception/ServiceUnavailableException.java b/main-app/src/main/java/com/backend/vue/bff/service/exception/ServiceUnavailableException.java new file mode 100644 index 0000000..e05b6d7 --- /dev/null +++ b/main-app/src/main/java/com/backend/vue/bff/service/exception/ServiceUnavailableException.java @@ -0,0 +1,7 @@ +package com.backend.vue.bff.service.exception; + +public class ServiceUnavailableException extends RuntimeException { + public ServiceUnavailableException() { + super("Service Unavailable"); + } +} diff --git a/src/main/resources/application.properties b/main-app/src/main/resources/application.properties similarity index 100% rename from src/main/resources/application.properties rename to main-app/src/main/resources/application.properties diff --git a/src/test/java/com/backend/extractor/kodik/service/anyame/bff/AnyameVueBffApplicationTests.java b/main-app/src/test/java/com/backend/vue/bff/service/AnyameVueBffApplicationTests.java similarity index 63% rename from src/test/java/com/backend/extractor/kodik/service/anyame/bff/AnyameVueBffApplicationTests.java rename to main-app/src/test/java/com/backend/vue/bff/service/AnyameVueBffApplicationTests.java index ac71c5e..5a90ccc 100644 --- a/src/test/java/com/backend/extractor/kodik/service/anyame/bff/AnyameVueBffApplicationTests.java +++ b/main-app/src/test/java/com/backend/vue/bff/service/AnyameVueBffApplicationTests.java @@ -1,4 +1,4 @@ -package com.backend.extractor.kodik.service.anyame.bff; +package com.backend.vue.bff.service; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -6,8 +6,8 @@ import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class AnyameVueBffApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/pom.xml b/pom.xml index 05940c8..409e230 100644 --- a/pom.xml +++ b/pom.xml @@ -1,19 +1,16 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.5.5 - - - com.backend.extractor.kodik.service - anyame-vue-bff + + com.backend.vue.bff.service + anyame-bff-parent 0.0.1-SNAPSHOT - anyame-vue-bff - BFF for Vue frontend + pom + anyame-bff-parent + Parent project for anyame-vue-bff + @@ -29,79 +26,15 @@ 21 - - 3.0.0 - 4.0.0 - 2.8.9 - 2025.0.0 - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-cache - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-circuitbreaker-resilience4j - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-server - - - - com.squareup.retrofit2 - converter-jackson - ${retrofit.version} - - - me.paulschwarz - spring-dotenv - ${spring-dotenv.version} - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - ${springdoc-openapi-starter.version} - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - + + anyame-kodik-search-api + main-app + + + + openapi-generator-repo + https://raw.githubusercontent.com/OpenAPITools/openapi-generator/master/maven-plugins/repository + +