diff --git a/application/src/main/java/com/techivw/webprice/application/ports/in/PriceServicePort.java b/application/src/main/java/com/techivw/webprice/application/ports/in/PriceServicePort.java new file mode 100644 index 0000000..1a37997 --- /dev/null +++ b/application/src/main/java/com/techivw/webprice/application/ports/in/PriceServicePort.java @@ -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); + +} diff --git a/application/src/main/java/com/techivw/webprice/application/ports/out/PriceRepositoryPort.java b/application/src/main/java/com/techivw/webprice/application/ports/out/PriceRepositoryPort.java new file mode 100644 index 0000000..b785a12 --- /dev/null +++ b/application/src/main/java/com/techivw/webprice/application/ports/out/PriceRepositoryPort.java @@ -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 findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId); + +} diff --git a/application/src/main/java/com/techivw/webprice/application/services/PriceService.java b/application/src/main/java/com/techivw/webprice/application/services/PriceService.java new file mode 100644 index 0000000..247c416 --- /dev/null +++ b/application/src/main/java/com/techivw/webprice/application/services/PriceService.java @@ -0,0 +1,21 @@ +package com.techivw.webprice.application.services; + +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).orElse(null); + } +} diff --git a/boot/src/main/resources/application.properties b/boot/src/main/resources/application.properties index 1981c62..c02d384 100644 --- a/boot/src/main/resources/application.properties +++ b/boot/src/main/resources/application.properties @@ -1 +1,12 @@ 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 diff --git a/domain/src/main/java/com/techivw/webprice/domain/Price.java b/domain/src/main/java/com/techivw/webprice/domain/Price.java new file mode 100644 index 0000000..fc8ba9a --- /dev/null +++ b/domain/src/main/java/com/techivw/webprice/domain/Price.java @@ -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; + +} diff --git a/infrastructure/in/rest-api/src/main/java/com/techivw/webprice/infrastructure/in/controllers/adapters/PriceControllerAdapter.java b/infrastructure/in/rest-api/src/main/java/com/techivw/webprice/infrastructure/in/controllers/adapters/PriceControllerAdapter.java new file mode 100644 index 0000000..cdb1c0f --- /dev/null +++ b/infrastructure/in/rest-api/src/main/java/com/techivw/webprice/infrastructure/in/controllers/adapters/PriceControllerAdapter.java @@ -0,0 +1,59 @@ +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 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 = "404", description = "Price information not found for the given parameters") + @ApiResponse(responseCode = "400", description = "Invalid request parameters") + public ResponseEntity 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); + PriceResponse priceResponse = PriceResponse.builder() + .productId(price.getProductId()) + .brandId(price.getBrandId()) + .priceList(price.getPriceList()) + .startDate(price.getStartDate()) + .endDate(price.getEndDate()) + .price(price.getPrice()) + .build(); + return ResponseEntity.ok(priceResponse); + } +} diff --git a/infrastructure/in/rest-api/src/main/java/com/techivw/webprice/infrastructure/in/controllers/model/PriceResponse.java b/infrastructure/in/rest-api/src/main/java/com/techivw/webprice/infrastructure/in/controllers/model/PriceResponse.java new file mode 100644 index 0000000..4c32826 --- /dev/null +++ b/infrastructure/in/rest-api/src/main/java/com/techivw/webprice/infrastructure/in/controllers/model/PriceResponse.java @@ -0,0 +1,17 @@ +package com.techivw.webprice.infrastructure.in.controllers.model; + +import lombok.Builder; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Builder +public class PriceResponse { + + private Long productId; + private Long brandId; + private Long priceList; + private LocalDateTime startDate; + private LocalDateTime endDate; + private BigDecimal price; +} diff --git a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/PriceEntityJPARepository.java b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/PriceEntityJPARepository.java new file mode 100644 index 0000000..f265993 --- /dev/null +++ b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/PriceEntityJPARepository.java @@ -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 { + + @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 findPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId); +} diff --git a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapter.java b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapter.java new file mode 100644 index 0000000..a30b161 --- /dev/null +++ b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapter.java @@ -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 findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) { + + Optional priceEntity = + repository.findPriceByDateTimeAndProductIdAndBrandId(dateTime, productId, brandId); + + return PriceEntityMapper.toOptionalPrice(priceEntity); + } +} diff --git a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/mappers/PriceEntityMapper.java b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/mappers/PriceEntityMapper.java new file mode 100644 index 0000000..7445cd9 --- /dev/null +++ b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/mappers/PriceEntityMapper.java @@ -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 toOptionalPrice(Optional priceEntity) { + return priceEntity.map(PriceEntityMapper::toPrice); + } + +} diff --git a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/model/PriceEntity.java b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/model/PriceEntity.java new file mode 100644 index 0000000..cba0dc6 --- /dev/null +++ b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/model/PriceEntity.java @@ -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; + +} diff --git a/pom.xml b/pom.xml index 4a35e15..a8adf7a 100644 --- a/pom.xml +++ b/pom.xml @@ -38,13 +38,22 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + org.projectlombok lombok - org.springframework.boot - spring-boot-starter-data-jpa + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.6