Compare commits

..

7 Commits

21 changed files with 1016 additions and 104 deletions

1
.env
View File

@ -1 +0,0 @@
SERVER_PORT=8090

View File

@ -1 +0,0 @@
SERVER_PORT=8090

2
.gitignore vendored
View File

@ -3,6 +3,8 @@ target/
.mvn/wrapper/maven-wrapper.jar .mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/ !**/src/main/**/target/
!**/src/test/**/target/ !**/src/test/**/target/
.mvn
/bin/
### STS ### ### STS ###
.apt_generated .apt_generated

View File

@ -2,16 +2,20 @@
FROM maven:3.9.6-eclipse-temurin-21 AS builder FROM maven:3.9.6-eclipse-temurin-21 AS builder
WORKDIR /workspace WORKDIR /workspace
COPY pom.xml . COPY pom.xml .
RUN mvn dependency:go-offline -B COPY main-app/pom.xml ./main-app/
COPY src ./src COPY anyame-kodik-search-api/pom.xml ./anyame-kodik-search-api/
RUN mvn clean package -DskipTests 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 # Create optimized runtime
FROM eclipse-temurin:21 AS app-build FROM eclipse-temurin:21 AS app-build
ENV RELEASE=21 ENV RELEASE=21
WORKDIR /opt/build 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 -Djarmode=layertools -jar application.jar extract
RUN $JAVA_HOME/bin/jlink \ RUN $JAVA_HOME/bin/jlink \

View File

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>anyame-bff-parent</artifactId>
<groupId>com.backend.vue.bff.service</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.backend.vue.bff.service</groupId>
<artifactId>anyame-kodik-search-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>anyame-kodik-search-api</name>
<description>Search API for anyame-kodik</description>
<url />
<licenses>
<license />
</licenses>
<developers>
<developer />
</developers>
<scm>
<connection />
<developerConnection />
<tag />
<url />
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<swagger-annotations-version>1.6.3</swagger-annotations-version>
<jackson-databind-version>2.17.1</jackson-databind-version>
<jackson-version>2.17.1</jackson-version>
<jakarta.ws.rs-api-version>4.0.0</jakarta.ws.rs-api-version>
<retrofit-version>2.11.0</retrofit-version>
<jakarta-annotation-version>2.1.1</jakarta-annotation-version>
<beanvalidation-version>3.0.2</beanvalidation-version>
<oltu-version>1.0.1</oltu-version>
<junit-version>5.10.3</junit-version>
<javax-annotation-api.version>1.3.2</javax-annotation-api.version>
<jakarta-validation-api.version>3.1.1</jakarta-validation-api.version>
<jsr305.version>3.0.2</jsr305.version>
</properties>
<dependencies>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations-version}</version>
</dependency>
<!-- @Nullable annotation -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>${retrofit-version}</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-scalars</artifactId>
<version>${retrofit-version}</version>
</dependency>
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.client</artifactId>
<version>${oltu-version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>common</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- JSON processing: jackson -->
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson</artifactId>
<version>${retrofit-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>${jakarta.ws.rs-api-version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta-annotation-version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax-annotation-api.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${jakarta-validation-api.version}</version>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.14.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi.json</inputSpec>
<generatorName>java</generatorName>
<configOptions>
<library>retrofit2</library>
<apiPackage>com.backend.search.kodik.api</apiPackage>
<modelPackage>com.backend.search.kodik.model</modelPackage>
<openApiNullable>false</openApiNullable>
<useJakartaEe>true</useJakartaEe>
<serializationLibrary>jackson</serializationLibrary>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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"
}
}
}
}
}
}

View File

@ -1,11 +1,14 @@
services: services:
vue-bff: anyame-vue-bff:
image: anyame-vue-bff:latest image: anyame-vue-bff:latest
ports: ports:
- 8090:8080 - 8090:8080
env_file: .env
networks: networks:
- anyame - anyame-shared
- elk-network
networks: networks:
anyame: anyame-shared:
driver: bridge external: true
elk-network:
external: true

118
main-app/pom.xml Normal file
View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.5</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.backend.vue.bff.service</groupId>
<artifactId>anyame-vue-bff</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>anyame-vue-bff</name>
<description>BFF for Vue frontend</description>
<url />
<licenses>
<license />
</licenses>
<developers>
<developer />
</developers>
<scm>
<connection />
<developerConnection />
<tag />
<url />
</scm>
<properties>
<java.version>21</java.version>
<retrofit.version>3.0.0</retrofit.version>
<spring-dotenv.version>4.0.0</spring-dotenv.version>
<springdoc-openapi-starter.version>2.8.9</springdoc-openapi-starter.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
<logstash-logback-encoder.version>8.1</logstash-logback-encoder.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson</artifactId>
<version>${retrofit.version}</version>
</dependency>
<dependency>
<groupId>me.paulschwarz</groupId>
<artifactId>spring-dotenv</artifactId>
<version>${spring-dotenv.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi-starter.version}</version>
</dependency>
<dependency>
<groupId>com.backend.vue.bff.service</groupId>
<artifactId>anyame-kodik-search-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${logstash-logback-encoder.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;

View File

@ -0,0 +1,17 @@
package com.backend.vue.bff.service.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("kodikId", "kodikSearch");
}
}

View File

@ -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<ErrorResponse> 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<ErrorResponse> 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<ErrorResponse> 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;
}
}
}

View File

@ -0,0 +1,66 @@
package com.backend.vue.bff.service.config;
import org.springframework.beans.factory.FactoryBean;
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.factory.AbstractApiFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import retrofit2.Retrofit.Builder;
@Configuration
@EnableDiscoveryClient
public class SearchApiClientConfiguration {
private final DiscoveryClient discoveryClient;
public SearchApiClientConfiguration(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
@Bean
@Lazy
public FactoryBean<ApiClient> apiClientFactory() {
return new ApiClientFactoryBean(discoveryClient);
}
@Bean
@Lazy
public SearchControllerApi searchApi(ApiClient apiClient) {
return apiClient.createService(SearchControllerApi.class);
}
private static class ApiClientFactoryBean extends AbstractApiFactory<ApiClient> {
public ApiClientFactoryBean(DiscoveryClient discoveryClient) {
super(discoveryClient, "ANYAME-KODIK-SEARCH-BACKEND");
}
@Override
public Class<?> getObjectType() {
return ApiClient.class;
}
@Override
public ObjectMapper createObjectMapper() {
JSON json = new JSON();
return json.getMapper();
}
@Override
public ApiClient createApiClient() {
return new ApiClient();
}
@Override
protected void updateAdapterBuilder(ApiClient instance, Builder adapterBuilder) {
instance.setAdapterBuilder(adapterBuilder);
}
}
}

View File

@ -0,0 +1,23 @@
package com.backend.vue.bff.service.controller;
import java.io.IOException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.backend.vue.bff.service.service.KodikService;
@RestController
public class TestController {
private final KodikService kodikService;
public TestController(KodikService kodikService) {
this.kodikService = kodikService;
}
@GetMapping("/test")
public String test(@RequestParam(name = "title") String title) throws IOException {
return Integer.toString(kodikService.search(title).getResults().size());
}
}

View File

@ -0,0 +1,7 @@
package com.backend.vue.bff.service.exception;
public class ServiceUnavailableException extends RuntimeException {
public ServiceUnavailableException() {
super("Service Unavailable");
}
}

View File

@ -0,0 +1,68 @@
package com.backend.vue.bff.service.factory;
import java.util.List;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import com.backend.vue.bff.service.exception.ServiceUnavailableException;
import com.fasterxml.jackson.databind.ObjectMapper;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
public abstract class AbstractApiFactory<T> implements FactoryBean<T> {
private final DiscoveryClient discoveryClient;
private final String serviceId;
public AbstractApiFactory(DiscoveryClient discoveryClient, String serviceId) {
this.discoveryClient = discoveryClient;
this.serviceId = serviceId;
}
@Override
public T getObject() throws Exception {
T client = createApiClient();
String baseUrl = resolveServiceUrl(serviceId);
Retrofit.Builder adapterBuilder = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(JacksonConverterFactory.create(createObjectMapper()));
updateAdapterBuilder(client, adapterBuilder);
return client;
}
@Override
public boolean isSingleton() {
return true;
}
private String resolveServiceUrl(String serviceName) {
List<ServiceInstance> 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;
}
public abstract ObjectMapper createObjectMapper();
public abstract T createApiClient();
protected abstract void updateAdapterBuilder(T instance, Retrofit.Builder adapterBuilder);
}

View File

@ -0,0 +1,25 @@
package com.backend.vue.bff.service.service;
import java.io.IOException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.backend.search.kodik.api.SearchControllerApi;
import com.backend.search.kodik.model.KodikResponse;
@Service
public class KodikService {
private final ObjectProvider<SearchControllerApi> controllerApiFactory;
public KodikService(ObjectProvider<SearchControllerApi> controllerApiFactory) {
this.controllerApiFactory = controllerApiFactory;
}
@Cacheable("kodikSearch")
public KodikResponse search(String title) throws BeansException, IOException {
return controllerApiFactory.getObject().search(title).execute().body();
}
}

View File

@ -0,0 +1,2 @@
spring.application.name=anyame-vue-bff
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka

View File

@ -0,0 +1,10 @@
<configuration>
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5044</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder" />
</appender>
<root level="INFO">
<appender-ref ref="LOGSTASH" />
</root>
</configuration>

View File

@ -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.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -6,8 +6,8 @@ import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest @SpringBootTest
class AnyameVueBffApplicationTests { class AnyameVueBffApplicationTests {
@Test @Test
void contextLoads() { void contextLoads() {
} }
} }

103
pom.xml
View File

@ -1,19 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId> <groupId>com.backend.vue.bff.service</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>anyame-bff-parent</artifactId>
<version>3.5.5</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.backend.extractor.kodik.service</groupId>
<artifactId>anyame-vue-bff</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<name>anyame-vue-bff</name> <packaging>pom</packaging>
<description>BFF for Vue frontend</description> <name>anyame-bff-parent</name>
<description>Parent project for anyame-vue-bff</description>
<url /> <url />
<licenses> <licenses>
<license /> <license />
@ -29,79 +26,15 @@
</scm> </scm>
<properties> <properties>
<java.version>21</java.version> <java.version>21</java.version>
<retrofit.version>3.0.0</retrofit.version>
<spring-dotenv.version>4.0.0</spring-dotenv.version>
<springdoc-openapi-starter.version>2.8.9</springdoc-openapi-starter.version>
<spring-cloud.version>2025.0.0</spring-cloud.version>
</properties> </properties>
<dependencies> <modules>
<dependency> <module>anyame-kodik-search-api</module>
<groupId>org.springframework.boot</groupId> <module>main-app</module>
<artifactId>spring-boot-starter-actuator</artifactId> </modules>
</dependency> <pluginRepositories>
<dependency> <pluginRepository>
<groupId>org.springframework.boot</groupId> <id>openapi-generator-repo</id>
<artifactId>spring-boot-starter-cache</artifactId> <url>https://raw.githubusercontent.com/OpenAPITools/openapi-generator/master/maven-plugins/repository</url>
</dependency> </pluginRepository>
<dependency> </pluginRepositories>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson</artifactId>
<version>${retrofit.version}</version>
</dependency>
<dependency>
<groupId>me.paulschwarz</groupId>
<artifactId>spring-dotenv</artifactId>
<version>${spring-dotenv.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi-starter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>

View File

@ -1,3 +0,0 @@
spring.application.name=anyame-vue-bff
server.port=${SERVER_PORT}
eureka.client.serviceUrl.defaultZone=http://localhost:${SERVER_PORT}/eureka/