chore(release): v1.0 #1
50
.gitignore
vendored
50
.gitignore
vendored
@ -1,26 +1,32 @@
|
|||||||
# ---> Java
|
target/
|
||||||
# Compiled class file
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
*.class
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
# Log file
|
### STS ###
|
||||||
*.log
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
# BlueJ files
|
### IntelliJ IDEA ###
|
||||||
*.ctxt
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
### NetBeans ###
|
||||||
.mtj.tmp/
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
# Package Files #
|
/dist/
|
||||||
*.jar
|
/nbdist/
|
||||||
*.war
|
/.nb-gradle/
|
||||||
*.nar
|
build/
|
||||||
*.ear
|
!**/src/main/**/build/
|
||||||
*.zip
|
!**/src/test/**/build/
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
||||||
replay_pid*
|
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|||||||
33
README.md
33
README.md
@ -1,2 +1,33 @@
|
|||||||
# web-price
|
Prices web service (web-price)
|
||||||
|
===================================
|
||||||
|
|
||||||
|
Web service for obtaining the price of a product given a date, the product id and the brand id.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Java 21
|
||||||
|
- Maven
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
1. Local installation of the project
|
||||||
|
```
|
||||||
|
mvn clean install
|
||||||
|
```
|
||||||
|
2. Run the app from the project root with:
|
||||||
|
```
|
||||||
|
mvn clean spring-boot:run -pl boot -Dspring-boot.run.profiles=local
|
||||||
|
```
|
||||||
|
or navigating to the `boot` directory of the project:
|
||||||
|
```
|
||||||
|
mvn clean spring-boot:run -Dspring-boot.run.profiles=local
|
||||||
|
```
|
||||||
|
3. Service should be ready to receive requests at http://localhost:8080/price with OpenAPI docs available at http://localhost:8080/swagger-ui/index.html
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
To run all tests in the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn test
|
||||||
|
```
|
||||||
|
|||||||
26
application/pom.xml
Normal file
26
application/pom.xml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<artifactId>application</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>application</name>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>web-price</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>domain</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.techivw.webprice.application.exceptions;
|
||||||
|
|
||||||
|
public class NotFoundException extends RuntimeException {
|
||||||
|
|
||||||
|
public NotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.techivw.webprice.application.ports.in;
|
||||||
|
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public interface PriceServicePort {
|
||||||
|
|
||||||
|
Price getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package com.techivw.webprice.application.ports.out;
|
||||||
|
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface PriceRepositoryPort {
|
||||||
|
|
||||||
|
Optional<Price> findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.techivw.webprice.application.services;
|
||||||
|
|
||||||
|
import com.techivw.webprice.application.exceptions.NotFoundException;
|
||||||
|
import com.techivw.webprice.application.ports.in.PriceServicePort;
|
||||||
|
import com.techivw.webprice.application.ports.out.PriceRepositoryPort;
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PriceService implements PriceServicePort {
|
||||||
|
|
||||||
|
PriceRepositoryPort priceRepositoryPort;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Price getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) {
|
||||||
|
return priceRepositoryPort.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(dateTime, productId, brandId)
|
||||||
|
.orElseThrow(() ->
|
||||||
|
new NotFoundException(String.format(
|
||||||
|
"Price for product %d of brand %d not found for date %s", productId, brandId, dateTime)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
package com.techivw.webprice.application.services;
|
||||||
|
|
||||||
|
import com.techivw.webprice.application.exceptions.NotFoundException;
|
||||||
|
import com.techivw.webprice.application.ports.in.PriceServicePort;
|
||||||
|
import com.techivw.webprice.application.ports.out.PriceRepositoryPort;
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
class PriceServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PriceRepositoryPort priceRepositoryPort;
|
||||||
|
|
||||||
|
private PriceServicePort priceService;
|
||||||
|
|
||||||
|
private final LocalDateTime TEST_DATE = LocalDateTime.parse("2023-01-01T12:00:00");
|
||||||
|
private final Long TEST_PRODUCT_ID = 35455L;
|
||||||
|
private final Long TEST_BRAND_ID = 1L;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
priceService = new PriceService(priceRepositoryPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnPriceWhenPriceExists() {
|
||||||
|
Price expectedPrice = createPrice();
|
||||||
|
when(priceRepositoryPort.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
||||||
|
eq(TEST_DATE), eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
|
.thenReturn(Optional.of(expectedPrice));
|
||||||
|
|
||||||
|
Price result = priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(
|
||||||
|
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(expectedPrice.getProductId(), result.getProductId());
|
||||||
|
assertEquals(expectedPrice.getPrice(), result.getPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowNotFoundExceptionWhenPriceDoesNotExist() {
|
||||||
|
when(priceRepositoryPort.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
||||||
|
any(LocalDateTime.class), anyLong(), anyLong()))
|
||||||
|
.thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
assertThrows(NotFoundException.class, () ->
|
||||||
|
priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(
|
||||||
|
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Price createPrice() {
|
||||||
|
return Price.builder()
|
||||||
|
.brandId(TEST_BRAND_ID)
|
||||||
|
.productId(TEST_PRODUCT_ID)
|
||||||
|
.startDate(TEST_DATE.minusDays(1))
|
||||||
|
.endDate(TEST_DATE.plusDays(1))
|
||||||
|
.priceList(1L)
|
||||||
|
.price(BigDecimal.valueOf(35.50))
|
||||||
|
.priority(1)
|
||||||
|
.currency("EUR")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
50
boot/pom.xml
Normal file
50
boot/pom.xml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?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>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>web-price</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>boot</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>boot</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Project modules -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>domain</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>application</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>rest-api</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>sql-repository</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
13
boot/src/main/java/com/techivw/webprice/Application.java
Normal file
13
boot/src/main/java/com/techivw/webprice/Application.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.techivw.webprice;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
boot/src/main/resources/application.properties
Normal file
17
boot/src/main/resources/application.properties
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
spring.application.name=webprice
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
spring.datasource.url=jdbc:h2:mem:webprice
|
||||||
|
spring.datasource.driverClassName=org.h2.Driver
|
||||||
|
spring.datasource.username=admin
|
||||||
|
spring.datasource.password=admin
|
||||||
|
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||||
|
|
||||||
|
# H2 console
|
||||||
|
spring.h2.console.enabled=true
|
||||||
|
spring.h2.console.path=/h2-console
|
||||||
|
|
||||||
|
# Flyway
|
||||||
|
spring.flyway.enabled=true
|
||||||
|
spring.flyway.locations=filesystem:infrastructure/out/sql-repository/sql/migration
|
||||||
|
spring.flyway.baseline-on-migrate=true
|
||||||
16
domain/pom.xml
Normal file
16
domain/pom.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<artifactId>domain</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>domain</name>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>web-price</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</parent>
|
||||||
|
</project>
|
||||||
23
domain/src/main/java/com/techivw/webprice/domain/Price.java
Normal file
23
domain/src/main/java/com/techivw/webprice/domain/Price.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package com.techivw.webprice.domain;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class Price {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long brandId;
|
||||||
|
private LocalDateTime startDate;
|
||||||
|
private LocalDateTime endDate;
|
||||||
|
private Long priceList;
|
||||||
|
private Long productId;
|
||||||
|
private Integer priority;
|
||||||
|
private BigDecimal price;
|
||||||
|
private String currency;
|
||||||
|
|
||||||
|
}
|
||||||
31
infrastructure/in/rest-api/pom.xml
Normal file
31
infrastructure/in/rest-api/pom.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?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>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>web-price</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>rest-api</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>rest-api</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>domain</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>application</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.in.controllers.adapters;
|
||||||
|
|
||||||
|
import com.techivw.webprice.application.ports.in.PriceServicePort;
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
import com.techivw.webprice.infrastructure.in.controllers.model.PriceResponse;
|
||||||
|
import com.techivw.webprice.infrastructure.in.controllers.model.mappers.PriceResponseMapper;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping("/price")
|
||||||
|
public class PriceControllerAdapter {
|
||||||
|
|
||||||
|
private PriceServicePort priceServicePort;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(
|
||||||
|
summary = "Get product price",
|
||||||
|
description = "Returns the current price for a specific product from a particular brand at a given date and time"
|
||||||
|
)
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Price information found",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = PriceResponse.class))
|
||||||
|
)
|
||||||
|
@ApiResponse(responseCode = "400", description = "Invalid request parameters")
|
||||||
|
@ApiResponse(responseCode = "404", description = "Price information not found for the given parameters")
|
||||||
|
public ResponseEntity<PriceResponse> getPrice(
|
||||||
|
@Parameter(description = "Date and time for price lookup (ISO format)", example = "2025-04-30T12:00:00")
|
||||||
|
@RequestParam(name = "dateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime,
|
||||||
|
@Parameter(description = "Product ID", example = "35455")
|
||||||
|
@RequestParam(name = "productId") Long productId,
|
||||||
|
@Parameter(description = "Brand ID", example = "1")
|
||||||
|
@RequestParam(name = "brandId") Long brandId) {
|
||||||
|
|
||||||
|
Price price = priceServicePort.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(dateTime, productId, brandId);
|
||||||
|
return ResponseEntity.ok(PriceResponseMapper.fromPrice(price));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.in.controllers.handlers;
|
||||||
|
|
||||||
|
import com.techivw.webprice.application.exceptions.NotFoundException;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class AdapterExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(NotFoundException.class)
|
||||||
|
public ResponseEntity<Map<String, Object>> handleNotFoundException(NotFoundException ex) {
|
||||||
|
Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("timestamp", new Date());
|
||||||
|
body.put("status", NOT_FOUND.value());
|
||||||
|
body.put("error", "Not Found");
|
||||||
|
body.put("message", ex.getMessage());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(body, NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.in.controllers.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class PriceResponse {
|
||||||
|
|
||||||
|
private Long productId;
|
||||||
|
private Long brandId;
|
||||||
|
private Long priceList;
|
||||||
|
private LocalDateTime startDate;
|
||||||
|
private LocalDateTime endDate;
|
||||||
|
private BigDecimal price;
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.in.controllers.model.mappers;
|
||||||
|
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
import com.techivw.webprice.infrastructure.in.controllers.model.PriceResponse;
|
||||||
|
|
||||||
|
public class PriceResponseMapper {
|
||||||
|
|
||||||
|
public static PriceResponse fromPrice(Price price) {
|
||||||
|
return PriceResponse.builder()
|
||||||
|
.productId(price.getProductId())
|
||||||
|
.brandId(price.getBrandId())
|
||||||
|
.priceList(price.getPriceList())
|
||||||
|
.startDate(price.getStartDate())
|
||||||
|
.endDate(price.getEndDate())
|
||||||
|
.price(price.getPrice())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.in.controllers.adapters;
|
||||||
|
|
||||||
|
import com.techivw.webprice.application.ports.in.PriceServicePort;
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@WebMvcTest
|
||||||
|
@ContextConfiguration(classes = PriceControllerAdapter.class)
|
||||||
|
@ActiveProfiles("test")
|
||||||
|
public class PriceControllerAdapterTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@MockitoBean
|
||||||
|
private PriceServicePort priceServicePort;
|
||||||
|
|
||||||
|
private static final Long PRODUCT_ID = 35455L;
|
||||||
|
private static final Long BRAND_ID = 1L;
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("testCases")
|
||||||
|
void testGetPrice(String testName, String dateTime, double expectedPrice) throws Exception {
|
||||||
|
LocalDateTime date = LocalDateTime.parse(dateTime);
|
||||||
|
|
||||||
|
Price mockPrice = createPrice(date.minusHours(1), date.plusHours(1), 1, expectedPrice, 1);
|
||||||
|
when(priceServicePort.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(date, PRODUCT_ID, BRAND_ID))
|
||||||
|
.thenReturn(mockPrice);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/price")
|
||||||
|
.param("dateTime", dateTime)
|
||||||
|
.param("productId", String.valueOf(PRODUCT_ID))
|
||||||
|
.param("brandId", String.valueOf(BRAND_ID)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.price").value(expectedPrice));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> testCases() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("Test 1", "2020-06-14T10:00:00", 35.50),
|
||||||
|
Arguments.of("Test 2", "2020-06-14T16:00:00", 25.45),
|
||||||
|
Arguments.of("Test 3", "2020-06-14T21:00:00", 35.50),
|
||||||
|
Arguments.of("Test 4", "2020-06-15T10:00:00", 30.50),
|
||||||
|
Arguments.of("Test 5", "2020-06-16T21:00:00", 38.95)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Price createPrice(LocalDateTime startDate, LocalDateTime endDate, long priceList, double price, int priority) {
|
||||||
|
return Price.builder()
|
||||||
|
.brandId(BRAND_ID)
|
||||||
|
.productId(PRODUCT_ID)
|
||||||
|
.startDate(startDate)
|
||||||
|
.endDate(endDate)
|
||||||
|
.priceList(priceList)
|
||||||
|
.price(BigDecimal.valueOf(price))
|
||||||
|
.priority(priority)
|
||||||
|
.currency("EUR")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
40
infrastructure/out/sql-repository/pom.xml
Normal file
40
infrastructure/out/sql-repository/pom.xml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<artifactId>sql-repository</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>sql-repository</name>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>web-price</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>domain</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>application</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-maven-plugin</artifactId>
|
||||||
|
<version>11.7.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE prices (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
brand_id BIGINT NOT NULL,
|
||||||
|
start_date TIMESTAMP NOT NULL,
|
||||||
|
end_date TIMESTAMP NOT NULL,
|
||||||
|
price_list BIGINT NOT NULL,
|
||||||
|
product_id BIGINT NOT NULL,
|
||||||
|
priority BIGINT NOT NULL,
|
||||||
|
price DECIMAL(10, 2) NOT NULL,
|
||||||
|
currency VARCHAR(3)
|
||||||
|
);
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
INSERT INTO prices (brand_id, start_date, end_date, price_list, product_id, priority, price, currency)
|
||||||
|
VALUES
|
||||||
|
(1, '2020-06-14 00:00:00', '2020-12-31 23:59:59', 1, 35455, 0, 35.50, 'EUR'),
|
||||||
|
(1, '2020-06-14 15:00:00', '2020-06-14 18:30:00', 2, 35455, 1, 25.45, 'EUR'),
|
||||||
|
(1, '2020-06-15 00:00:00', '2020-06-15 11:00:00', 3, 35455, 1, 30.50, 'EUR'),
|
||||||
|
(1, '2020-06-15 16:00:00', '2020-12-31 23:59:59', 4, 35455, 1, 38.95, 'EUR');
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.repositories;
|
||||||
|
|
||||||
|
import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface PriceEntityJPARepository extends JpaRepository<PriceEntity, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT p FROM PriceEntity p WHERE " +
|
||||||
|
"p.productId = :productId " +
|
||||||
|
"AND p.brandId = :brandId " +
|
||||||
|
"AND :dateTime BETWEEN p.startDate AND p.endDate " +
|
||||||
|
"ORDER BY p.priority DESC LIMIT 1")
|
||||||
|
Optional<PriceEntity> findPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId);
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.repositories.adapters;
|
||||||
|
|
||||||
|
import com.techivw.webprice.application.ports.out.PriceRepositoryPort;
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
import com.techivw.webprice.infrastructure.repositories.PriceEntityJPARepository;
|
||||||
|
import com.techivw.webprice.infrastructure.repositories.mappers.PriceEntityMapper;
|
||||||
|
import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PriceRepositoryAdapter implements PriceRepositoryPort {
|
||||||
|
|
||||||
|
private final PriceEntityJPARepository repository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Price> findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) {
|
||||||
|
|
||||||
|
Optional<PriceEntity> priceEntity =
|
||||||
|
repository.findPriceByDateTimeAndProductIdAndBrandId(dateTime, productId, brandId);
|
||||||
|
|
||||||
|
return PriceEntityMapper.toOptionalPrice(priceEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.repositories.mappers;
|
||||||
|
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class PriceEntityMapper {
|
||||||
|
|
||||||
|
public static Price toPrice(PriceEntity priceEntity) {
|
||||||
|
return Price.builder()
|
||||||
|
.id(priceEntity.getId())
|
||||||
|
.brandId(priceEntity.getBrandId())
|
||||||
|
.startDate(priceEntity.getStartDate())
|
||||||
|
.endDate(priceEntity.getEndDate())
|
||||||
|
.priceList(priceEntity.getPriceList())
|
||||||
|
.productId(priceEntity.getProductId())
|
||||||
|
.priority(priceEntity.getPriority())
|
||||||
|
.price(priceEntity.getPrice())
|
||||||
|
.currency(priceEntity.getCurrency())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<Price> toOptionalPrice(Optional<PriceEntity> priceEntity) {
|
||||||
|
return priceEntity.map(PriceEntityMapper::toPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.repositories.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Table(name = "prices")
|
||||||
|
public class PriceEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "brand_id")
|
||||||
|
private Long brandId;
|
||||||
|
|
||||||
|
@Column(name = "start_date")
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
private LocalDateTime startDate;
|
||||||
|
|
||||||
|
@Column(name = "end_date")
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
private LocalDateTime endDate;
|
||||||
|
|
||||||
|
@Column(name = "price_list")
|
||||||
|
private Long priceList;
|
||||||
|
|
||||||
|
@Column(name = "product_id")
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@Column(name = "priority")
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
@Column(name = "price")
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
@Column(name = "currency")
|
||||||
|
private String currency;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
package com.techivw.webprice.infrastructure.repositories.adapters;
|
||||||
|
|
||||||
|
import com.techivw.webprice.domain.Price;
|
||||||
|
import com.techivw.webprice.infrastructure.repositories.PriceEntityJPARepository;
|
||||||
|
import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class PriceRepositoryAdapterTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PriceEntityJPARepository repository;
|
||||||
|
|
||||||
|
private PriceRepositoryAdapter adapter;
|
||||||
|
|
||||||
|
private final LocalDateTime TEST_DATE = LocalDateTime.parse("2023-01-01T12:00:00");
|
||||||
|
private final Long TEST_PRODUCT_ID = 35455L;
|
||||||
|
private final Long TEST_BRAND_ID = 1L;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
adapter = new PriceRepositoryAdapter(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnPriceWhenPriceEntityExists() {
|
||||||
|
PriceEntity priceEntity = createSamplePriceEntity();
|
||||||
|
when(repository.findPriceByDateTimeAndProductIdAndBrandId(
|
||||||
|
eq(TEST_DATE), eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
|
.thenReturn(Optional.of(priceEntity));
|
||||||
|
|
||||||
|
Optional<Price> result = adapter.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
||||||
|
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||||
|
|
||||||
|
assertTrue(result.isPresent());
|
||||||
|
Price price = result.get();
|
||||||
|
assertEquals(TEST_PRODUCT_ID, price.getProductId());
|
||||||
|
assertEquals(TEST_BRAND_ID, price.getBrandId());
|
||||||
|
assertEquals(BigDecimal.valueOf(35.50), price.getPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnEmptyOptionalWhenPriceEntityDoesNotExist() {
|
||||||
|
when(repository.findPriceByDateTimeAndProductIdAndBrandId(
|
||||||
|
any(LocalDateTime.class), anyLong(), anyLong()))
|
||||||
|
.thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
Optional<Price> result = adapter.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
||||||
|
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||||
|
|
||||||
|
assertFalse(result.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PriceEntity createSamplePriceEntity() {
|
||||||
|
PriceEntity entity = new PriceEntity();
|
||||||
|
entity.setBrandId(TEST_BRAND_ID);
|
||||||
|
entity.setProductId(TEST_PRODUCT_ID);
|
||||||
|
entity.setStartDate(TEST_DATE.minusDays(1));
|
||||||
|
entity.setEndDate(TEST_DATE.plusDays(1));
|
||||||
|
entity.setPriceList(1L);
|
||||||
|
entity.setPrice(BigDecimal.valueOf(35.50));
|
||||||
|
entity.setPriority(1);
|
||||||
|
entity.setCurrency("EUR");
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
73
pom.xml
Normal file
73
pom.xml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>domain</module>
|
||||||
|
<module>application</module>
|
||||||
|
<module>infrastructure/in/rest-api</module>
|
||||||
|
<module>infrastructure/out/sql-repository</module>
|
||||||
|
<module>boot</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.4.4</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.techivw</groupId>
|
||||||
|
<artifactId>web-price</artifactId>
|
||||||
|
<version>1.0-RELEASE</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>web-price</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Project dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.8.6</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<argLine>-javaagent:${settings.localRepository}/net/bytebuddy/byte-buddy-agent/1.15.11/byte-buddy-agent-1.15.11.jar</argLine>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
Loading…
x
Reference in New Issue
Block a user