Skip to content

Commit 92526ce

Browse files
committed
Implement the createProducts feature
1 parent 1a0f66f commit 92526ce

29 files changed

+652
-40
lines changed

catalog-service/src/main/java/com/example/catalog_service/controller/ProductController.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package com.example.catalog_service.controller;
22

3-
import com.example.catalog_service.dto.PagedResult;
4-
import com.example.catalog_service.dto.ProductCreationRequest;
5-
import com.example.catalog_service.dto.ProductResponse;
6-
import com.example.catalog_service.dto.ProductUpdateRequest;
3+
import com.example.catalog_service.dto.*;
4+
import com.example.catalog_service.service.ProductCreationBulkService;
75
import com.example.catalog_service.service.ProductService;
86
import java.math.BigDecimal;
97
import lombok.RequiredArgsConstructor;
@@ -23,6 +21,7 @@
2321
@Slf4j
2422
public class ProductController {
2523
private final ProductService service;
24+
private final ProductCreationBulkService bulkService;
2625

2726
@GetMapping
2827
public Mono<ResponseEntity<PagedResult<ProductResponse>>> getProducts(
@@ -49,6 +48,13 @@ public Mono<ResponseEntity<ProductResponse>> create(@RequestBody Mono<ProductCre
4948
.map(dto -> ResponseEntity.status(HttpStatus.CREATED).body(dto));
5049
}
5150

51+
@PostMapping("/bulk")
52+
public Mono<ResponseEntity<ProductCreationBulkResponse>> createProducts(
53+
@RequestParam() String filePath) {
54+
return bulkService.createProducts(filePath)
55+
.map(dto -> ResponseEntity.status(HttpStatus.OK).body(dto));
56+
}
57+
5258
@PutMapping("/{code}")
5359
public Mono<ResponseEntity<ProductResponse>> update(@PathVariable String code,@RequestBody Mono<ProductUpdateRequest> request) {
5460
return service.update(code, request)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.example.catalog_service.dto;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record ProductCreationBulkErrorDetail(
7+
int rowNumber,
8+
String errorMessage
9+
) {
10+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.example.catalog_service.dto;
2+
3+
import lombok.*;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
8+
@Builder
9+
@Getter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
@Setter
13+
public class ProductCreationBulkResponse {
14+
int processed;
15+
int success;
16+
List<ProductResponse> createdProducts;
17+
int failure;
18+
List<ProductCreationBulkErrorDetail> errorDetails;
19+
20+
21+
public void incrementSuccess() {
22+
success++;
23+
}
24+
25+
public void incrementFailure() {
26+
failure++;
27+
}
28+
29+
public void incrementProcessed() {
30+
processed++;
31+
}
32+
33+
public void addCreatedProduct(ProductResponse productResponse) {
34+
createdProducts.add(productResponse);
35+
}
36+
37+
public void addErrorDetail(int rowNumber, String errorMessage) {
38+
errorDetails.add(new ProductCreationBulkErrorDetail(rowNumber, errorMessage));
39+
}
40+
}

catalog-service/src/main/java/com/example/catalog_service/service/InventoryService.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ public Mono<PurchaseDTO> processRequest(PurchaseDTO.Request request) {
5353
.switchIfEmpty(ApplicationsExceptions.notEnoughInventory(request.productId()))
5454
.zipWhen(p -> Mono.fromSupplier(() -> Mapper.toProductInventoryEntity().apply(request)),executeProcess())
5555
.flatMap(Function.identity());
56-
5756
}
5857

5958
/**
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.example.catalog_service.service;
2+
3+
import com.example.catalog_service.dto.ProductCreationBulkResponse;
4+
import com.example.catalog_service.dto.ProductCreationRequest;
5+
import com.example.catalog_service.dto.ProductResponse;
6+
import com.example.catalog_service.util.Util;
7+
import lombok.RequiredArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.stereotype.Service;
10+
import org.springframework.transaction.annotation.Transactional;
11+
import reactor.core.publisher.Flux;
12+
import reactor.core.publisher.Mono;
13+
14+
import java.io.BufferedReader;
15+
import java.io.FileReader;
16+
import java.math.BigDecimal;
17+
import java.util.ArrayList;
18+
import java.util.concurrent.atomic.AtomicInteger;
19+
import java.util.function.Function;
20+
21+
@Transactional
22+
@RequiredArgsConstructor
23+
@Service
24+
@Slf4j
25+
public class ProductCreationBulkService {
26+
private final ProductService service;
27+
28+
private final ProductCreationBulkResponse response = ProductCreationBulkResponse.builder()
29+
.failure(0)
30+
.success(0)
31+
.processed(0)
32+
.createdProducts(new ArrayList<>())
33+
.errorDetails(new ArrayList<>())
34+
.build();
35+
36+
private final AtomicInteger rowNumber = new AtomicInteger(0);
37+
38+
39+
public Mono<ProductCreationBulkResponse> createProducts(String filePath) {
40+
return processFile(filePath)
41+
.then(Mono.fromSupplier(() ->response))
42+
.doOnNext(dto ->log.info("The Create Products Operation Resulted in: {}", Util.write(dto)));
43+
}
44+
45+
private Flux<ProductResponse> processFile(String filePath) {
46+
log.info("Processing file: {}", filePath);
47+
return Flux.<String>create(sink -> {
48+
try(var reader = new BufferedReader(new FileReader(filePath))) {
49+
String line;
50+
while ((line = reader.readLine()) != null) {
51+
response.incrementProcessed();
52+
sink.next(line);
53+
}
54+
sink.complete();
55+
}catch (Exception e) {
56+
log.error("Error While Processing File : {}", e.getMessage());
57+
}
58+
})
59+
.flatMap(line -> {
60+
log.info("Received Line: {}",line);
61+
var rowValue = incrementAndGet();
62+
return service.createProduct(Mono.fromSupplier(() -> createRequest(line)))
63+
.onErrorContinue((ex,__) -> {
64+
handleFailure(ex, rowValue);
65+
log.info("Adding Error Detail Because of: {}", ex.getMessage());
66+
})
67+
.doOnSuccess(productResponse -> {
68+
handleSuccess(productResponse);
69+
log.info("Added Product Response: {}", productResponse);
70+
});
71+
});
72+
}
73+
74+
private Integer incrementAndGet(){
75+
return rowNumber.incrementAndGet();
76+
}
77+
78+
79+
private ProductCreationRequest createRequest(String line){
80+
var array = line.split(",");
81+
return ProductCreationRequest.builder()
82+
.code(array[0])
83+
.name(array[1])
84+
.description(array[2])
85+
.price(new BigDecimal(array[3]))
86+
.quantity(Integer.parseInt(array[4]))
87+
.imageUrl(array[5])
88+
.build();
89+
}
90+
91+
private void handleFailure(Throwable ex, int rowNumber){
92+
response.incrementFailure();
93+
response.addErrorDetail(rowNumber, ex.getMessage());
94+
}
95+
96+
private void handleSuccess(ProductResponse productResponse){
97+
response.incrementSuccess();
98+
response.addCreatedProduct(productResponse);
99+
}
100+
101+
102+
}

catalog-service/src/test/java/com/example/catalog_service/AbstractIntegrationTest.java renamed to catalog-service/src/test/java/com/example/catalog_service/integration_tests/AbstractIntegrationTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
package com.example.catalog_service;
1+
package com.example.catalog_service.integration_tests;
22

33

44
import java.util.Arrays;
55

6-
import com.example.catalog_service.domain.Product;
76
import com.example.catalog_service.repo.ProductInventoryRepo;
87
import com.example.catalog_service.repo.ProductRepo;
98
import org.junit.jupiter.api.BeforeEach;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package com.example.catalog_service.CRUD_abstract_tests;
1+
package com.example.catalog_service.integration_tests.CRUD_abstract_tests;
22

3-
import com.example.catalog_service.AbstractIntegrationTest;
3+
import com.example.catalog_service.integration_tests.AbstractIntegrationTest;
44
import org.springframework.http.ProblemDetail;
55
import reactor.test.StepVerifier;
66

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package com.example.catalog_service.CRUD_abstract_tests;
1+
package com.example.catalog_service.integration_tests.CRUD_abstract_tests;
22

3-
import com.example.catalog_service.AbstractIntegrationTest;
3+
import com.example.catalog_service.integration_tests.AbstractIntegrationTest;
44
import com.example.catalog_service.dto.ProductCreationRequest;
55
import com.example.catalog_service.dto.ProductResponse;
66
import org.apache.logging.log4j.util.TriConsumer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.example.catalog_service.integration_tests.CRUD_abstract_tests;
2+
3+
import com.example.catalog_service.dto.ProductCreationBulkResponse;
4+
import com.example.catalog_service.integration_tests.AbstractIntegrationTest;
5+
import reactor.test.StepVerifier;
6+
7+
import java.time.Duration;
8+
import java.util.function.BiConsumer;
9+
import java.util.function.Consumer;
10+
11+
public class AbstractProductBulkPostRequestTests extends AbstractIntegrationTest {
12+
13+
protected BiConsumer<String, Consumer<ProductCreationBulkResponse>> test(){
14+
return (filePath, consumer) -> client
15+
.post()
16+
.uri(uriBuilder -> uriBuilder
17+
.path("/api/products/bulk")
18+
.queryParam("filePath", filePath)
19+
.build())
20+
.exchange()
21+
.expectStatus().isOk()
22+
.returnResult(ProductCreationBulkResponse.class)
23+
.getResponseBody()
24+
.as(StepVerifier::create)
25+
.consumeNextWith(consumer)
26+
.verifyComplete();
27+
}
28+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package com.example.catalog_service.CRUD_abstract_tests;
1+
package com.example.catalog_service.integration_tests.CRUD_abstract_tests;
22

3-
import com.example.catalog_service.AbstractIntegrationTest;
3+
import com.example.catalog_service.integration_tests.AbstractIntegrationTest;
44
import com.example.catalog_service.dto.ProductResponse;
55
import com.example.catalog_service.dto.ProductUpdateRequest;
66
import org.apache.logging.log4j.util.TriConsumer;

0 commit comments

Comments
 (0)