Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1223 Implement mock APIs for delivery service #1231

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,28 @@ YAS_SERVICES_INVENTORY=http://inventory/inventory
YAS_SERVICES_RATING=http://rating/rating
YAS_SERVICES_SAMPLE_DATA=http://sampledata/sampledata
YAS_SERVICES_RECOMMENDATION=http://recommendation/recommendation
YAS_SERVICES_DELIVERY=http://delivery/delivery

SERVER_PORT=80

# Swagger UI
URLS=[{ url: 'http://api.yas.local/product/v3/api-docs', name: 'Product' },{ url: 'http://api.yas.local/media/v3/api-docs', name: 'Media' },{ url: 'http://api.yas.local/customer/v3/api-docs', name: 'Customer' },{ url: 'http://api.yas.local/cart/v3/api-docs', name: 'Cart'},{ url: 'http://api.yas.local/rating/v3/api-docs', name: 'Rating' }, { url: 'http://api.yas.local/order/v3/api-docs', name: 'Order'},{ url: 'http://api.yas.local/payment/v3/api-docs', name: 'Payment'},{ url: 'http://api.yas.local/payment-paypal/v3/api-docs', name: 'Payment-paypal'},{ url: 'http://api.yas.local/location/v3/api-docs', name: 'Location'}, { url: 'http://api.yas.local/inventory/v3/api-docs', name: 'Inventory'},{ url: 'http://api.yas.local/tax/v3/api-docs', name: 'Tax' },{ url: 'http://api.yas.local/promotion/v3/api-docs', name: 'Promotion'},{ url: 'http://api.yas.local/search/v3/api-docs', name: 'Search'}, { url: 'http://api.yas.local/webhook/v3/api-docs', name: 'Webhook'}]
URLS='[
{ url: "http://api.yas.local/product/v3/api-docs", name: "Product" },
{ url: "http://api.yas.local/media/v3/api-docs", name: "Media" },
{ url: "http://api.yas.local/customer/v3/api-docs", name: "Customer" },
{ url: "http://api.yas.local/cart/v3/api-docs", name: "Cart" },
{ url: "http://api.yas.local/rating/v3/api-docs", name: "Rating" },
{ url: "http://api.yas.local/order/v3/api-docs", name: "Order" },
{ url: "http://api.yas.local/payment/v3/api-docs", name: "Payment" },
{ url: "http://api.yas.local/payment-paypal/v3/api-docs", name: "Payment-paypal" },
{ url: "http://api.yas.local/location/v3/api-docs", name: "Location" },
{ url: "http://api.yas.local/inventory/v3/api-docs", name: "Inventory" },
{ url: "http://api.yas.local/tax/v3/api-docs", name: "Tax" },
{ url: "http://api.yas.local/promotion/v3/api-docs", name: "Promotion" },
{ url: "http://api.yas.local/search/v3/api-docs", name: "Search" },
{ url: "http://api.yas.local/webhook/v3/api-docs", name: "Webhook" },
{ url: "http://api.yas.local/delivery/v3/api-docs", name: "Delivery" }
]'

# Start all service when run docker compose up
COMPOSE_FILE=docker-compose.yml:docker-compose.search.yml:docker-compose.o11y.yml
Expand Down
45 changes: 45 additions & 0 deletions delivery/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,49 @@
<sonar.projectKey>nashtech-garage_yas-delivery</sonar.projectKey>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.yas</groupId>
<artifactId>common-library</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.yas</groupId>
<artifactId>common-library</artifactId>
<version>${revision}</version>
<classifier>tests</classifier>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@SpringBootApplication(scanBasePackages = {"com.yas.delivery", "com.yas.commonlibrary"})
public class DeliveryApplication {
public static void main(String[] args) {
SpringApplication.run(DeliveryApplication.class, args);
Expand Down
48 changes: 48 additions & 0 deletions delivery/src/main/java/com/yas/delivery/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.yas.delivery.config;

import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/prometheus", "/actuator/health/**",
"/swagger-ui", "/swagger-ui/**", "/error", "/v3/api-docs/**").permitAll()
.requestMatchers("/storefront/**").permitAll()
.requestMatchers("/backoffice/**").hasRole("ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() {
Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter = jwt -> {
Map<String, Collection<String>> realmAccess = jwt.getClaim("realm_access");
Collection<String> roles = realmAccess.get("roles");
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
};

var jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);

return jwtAuthenticationConverter;
}
}
24 changes: 24 additions & 0 deletions delivery/src/main/java/com/yas/delivery/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.yas.delivery.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.OAuthFlow;
import io.swagger.v3.oas.annotations.security.OAuthFlows;
import io.swagger.v3.oas.annotations.security.OAuthScope;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.annotations.servers.Server;

@OpenAPIDefinition(info = @Info(title = "Delivery Service API", description = "Delivery API documentation",
version = "1.0"),
security = @SecurityRequirement(name = "oauth2_bearer"),
servers = {@Server(url = "${server.servlet.context-path}", description = "Default Server URL")})
@SecurityScheme(name = "oauth2_bearer", type = SecuritySchemeType.OAUTH2,
flows = @OAuthFlows(authorizationCode =
@OAuthFlow(authorizationUrl = "${springdoc.oauthflow.authorization-url}",
tokenUrl = "${springdoc.oauthflow.token-url}", scopes = {
@OAuthScope(name = "openid", description = "openid")
})))
public class SwaggerConfig {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
package com.yas.delivery.controller;

import com.yas.delivery.service.DeliveryService;
import com.yas.delivery.viewmodel.CalculateFeesPostVm;
import com.yas.delivery.viewmodel.ShipmentFeeVm;
import com.yas.delivery.viewmodel.ShipmentProviderVm;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class DeliveryController {
private final DeliveryService deliveryService;

@GetMapping("/storefront/shipment/providers")
public ResponseEntity<List<ShipmentProviderVm>> getShipmentProviders() {
List<ShipmentProviderVm> shipmentProviders = deliveryService.getShipmentProviders();
return ResponseEntity.ok(shipmentProviders);
}

@PostMapping("/storefront/shipment/calculate")
public ResponseEntity<List<ShipmentFeeVm>> calculateShipmentFees(
@RequestBody CalculateFeesPostVm calculateFeePostVm) {
List<ShipmentFeeVm> shipmentFees = deliveryService.calculateShipmentFees(calculateFeePostVm);
return ResponseEntity.ok(shipmentFees);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.yas.delivery.model;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@lombok.Getter
@lombok.Setter
@Builder
public class ShipmentProvider {
private String id;
private String name;
private List<ShipmentServiceType> serviceTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.yas.delivery.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@lombok.Getter
@lombok.Setter
@Builder
public class ShipmentServiceType {
private String id;
private String name;
private double cost;
private double tax;
private String expectedDeliveryTime;
}
131 changes: 131 additions & 0 deletions delivery/src/main/java/com/yas/delivery/service/DeliveryService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,138 @@
package com.yas.delivery.service;

import com.yas.commonlibrary.exception.NotFoundException;
import com.yas.delivery.model.ShipmentProvider;
import com.yas.delivery.model.ShipmentServiceType;
import com.yas.delivery.viewmodel.CalculateFeesPostVm;
import com.yas.delivery.viewmodel.CheckoutItemVm;
import com.yas.delivery.viewmodel.ShipmentFeeVm;
import com.yas.delivery.viewmodel.ShipmentProviderVm;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;

@Service
public class DeliveryService {
private static final List<ShipmentProvider> shipmentProviders;

static {
// Initialize shipment providers and service types
ShipmentProvider fedexProvider = buildFedexProvider();
ShipmentProvider upsProvider = buildUPSProvider();

shipmentProviders = Arrays.asList(fedexProvider, upsProvider);
}

private static ShipmentProvider buildFedexProvider() {
ZonedDateTime now = ZonedDateTime.now();

ShipmentProvider fedexProvider = new ShipmentProvider();
fedexProvider.setId("FEDEX");
fedexProvider.setName("Fedex");
List<ShipmentServiceType> fedexServiceTypes = Arrays.asList(
ShipmentServiceType.builder()
.id("FEDEX_INTERNATIONAL_PRIORITY")
.name("FedEx International Priority")
.cost(20.0)
.tax(2.0)
.expectedDeliveryTime(now.plusDays(1).format(DateTimeFormatter.ISO_INSTANT))
.build(),
ShipmentServiceType.builder()
.id("INTERNATIONAL_ECONOMY")
.name("FedEx International Economy")
.cost(30.0)
.tax(3.0)
.expectedDeliveryTime(now.plusDays(3).format(DateTimeFormatter.ISO_INSTANT))
.build()
);
fedexProvider.setServiceTypes(fedexServiceTypes);
return fedexProvider;
}

private static ShipmentProvider buildUPSProvider() {
ZonedDateTime now = ZonedDateTime.now();

ShipmentProvider upsProvider = new ShipmentProvider();
upsProvider.setId("UPS");
upsProvider.setName("UPS");
List<ShipmentServiceType> upsServiceTypes = Arrays.asList(
ShipmentServiceType.builder()
.id("07")
.name("UPS Worldwide Express")
.cost(10.0)
.tax(1.0)
.expectedDeliveryTime(now.plusDays(5).format(DateTimeFormatter.ISO_INSTANT))
.build(),
ShipmentServiceType.builder()
.id("11")
.name("UPS Standard")
.cost(15.0)
.expectedDeliveryTime(now.plusDays(7).format(DateTimeFormatter.ISO_INSTANT))
.tax(1.5)
.build()
);
upsProvider.setServiceTypes(upsServiceTypes);
return upsProvider;
}

/**
* Retrieves a list of available shipment providers.
*
* @return a list of {@link ShipmentProviderVm} representing the available shipment providers.
*/
public List<ShipmentProviderVm> getShipmentProviders() {
return shipmentProviders.stream().map(shipmentProvider ->
new ShipmentProviderVm(shipmentProvider.getId(), shipmentProvider.getName())
).toList();
}

/**
* Calculates the shipment fees for the given shipment provider based on checkout items.
*
* @param calculateFeePostVm the view model containing the necessary data for fee calculation.
* @return a list of {@link ShipmentFeeVm} representing the calculated shipment fees for each checkout item.
*/
public List<ShipmentFeeVm> calculateShipmentFees(CalculateFeesPostVm calculateFeePostVm) {
ShipmentProvider shipmentProvider =
findShipmentProviderById(calculateFeePostVm.shipmentProviderId())
.orElseThrow(() -> new NotFoundException("Invalid shipment provider"));

List<ShipmentFeeVm> shipmentFees = new ArrayList<>();

for (CheckoutItemVm checkoutItem : calculateFeePostVm.checkoutItems()) {
shipmentFees.addAll(calculateShipmentFeesForCheckoutItem(shipmentProvider, checkoutItem));
}
return shipmentFees;
}

private List<ShipmentFeeVm> calculateShipmentFeesForCheckoutItem(ShipmentProvider shipmentProvider,
CheckoutItemVm checkoutItem) {

// To make the response more dynamic, we calculate the shipment fees by multiplying the cost and tax with
// the quantity of the checkout item.
// The actual calculation should be based on the actual business logic.
return shipmentProvider.getServiceTypes().stream()
.map(serviceType ->
ShipmentFeeVm.builder()
.checkoutItemId(checkoutItem.id())
.shipmentProviderId(shipmentProvider.getId())
.shipmentProviderName(shipmentProvider.getName())
.shipmentServiceTypeId(serviceType.getId())
.shipmentServiceTypeName(serviceType.getName())
.shipmentCost(serviceType.getCost() * checkoutItem.quantity())
.shipmentTax(serviceType.getTax() * checkoutItem.quantity())
.expectedDeliveryTime(serviceType.getExpectedDeliveryTime())
.build()
).toList();
}

private Optional<ShipmentProvider> findShipmentProviderById(String shipmentProviderId) {
return shipmentProviders.stream()
.filter(provider -> provider.getId().equals(shipmentProviderId))
.findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.yas.delivery.viewmodel;

import java.util.List;

public record CalculateFeesPostVm(String shipmentProviderId,
String recipientAddressId,
List<CheckoutItemVm> checkoutItems) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.yas.delivery.viewmodel;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public record CheckoutItemVm(@NotNull String id,
@NotNull String productId,
@NotNull @Min(1) Integer quantity) {
}
Loading