├── product-catalog-svc ├── src │ └── main │ │ ├── resources │ │ └── application.properties │ │ └── java │ │ └── dev │ │ └── techdozo │ │ └── product │ │ ├── application │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── ProductStatus.java │ │ │ │ ├── Product.java │ │ │ │ └── ProductCatalog.java │ │ │ └── repository │ │ │ │ └── ProductCatalogRepository.java │ │ ├── query │ │ │ ├── model │ │ │ │ └── QueryParameter.java │ │ │ ├── ProductCatalogQuery.java │ │ │ └── impl │ │ │ │ └── ProductCatalogQueryImpl.java │ │ ├── command │ │ │ ├── ProductCatalogCommand.java │ │ │ └── impl │ │ │ │ └── ProductCatalogCommandImpl.java │ │ └── factory │ │ │ ├── ProductCatalogFactory.java │ │ │ └── impl │ │ │ └── ProductCatalogFactoryImpl.java │ │ ├── common │ │ ├── error │ │ │ ├── InvalidOperationException.java │ │ │ ├── ResourceNotFoundException.java │ │ │ ├── ErrorResponse.java │ │ │ └── handler │ │ │ │ └── GlobalExceptionHandler.java │ │ └── config │ │ │ └── AppConfig.java │ │ ├── resource │ │ ├── model │ │ │ ├── ProductResponse.java │ │ │ ├── GetProductResponse.java │ │ │ └── ProductRequest.java │ │ ├── mapper │ │ │ └── ProductRequestMapper.java │ │ └── ProductCatalogController.java │ │ ├── ProductCatalogSvcApplication.java │ │ └── persistence │ │ ├── repository │ │ ├── jpa │ │ │ └── ProductCatalogJpaRepository.java │ │ ├── model │ │ │ └── ProductPersistable.java │ │ └── ProductCatalogRepositoryImpl.java │ │ └── mapper │ │ └── ProductPersistableMapper.java ├── settings.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── Dockerfile ├── kubernetes │ ├── deployments │ │ ├── service.yaml │ │ ├── ingress.yaml │ │ └── deployment.yaml │ ├── helm │ │ └── product-catalog │ │ │ ├── templates │ │ │ ├── serviceaccount.yaml │ │ │ ├── service.yaml │ │ │ ├── tests │ │ │ │ └── test-connection.yaml │ │ │ ├── hpa.yaml │ │ │ ├── NOTES.txt │ │ │ ├── deployment.yaml │ │ │ ├── _helpers.tpl │ │ │ └── ingress.yaml │ │ │ ├── .helmignore │ │ │ ├── Chart.yaml │ │ │ └── values.yaml │ └── kind │ │ └── kind-config.yaml ├── .gitignore ├── build.gradle ├── gradlew.bat ├── gradlew ├── openApi.yaml └── collections │ └── Product catalog service.postman_collection.json └── README.md /product-catalog-svc/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /product-catalog-svc/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'product-catalog-svc' 2 | -------------------------------------------------------------------------------- /product-catalog-svc/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techdozo/microservices/HEAD/product-catalog-svc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /product-catalog-svc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11-jre-slim 2 | RUN mkdir /app 3 | WORKDIR /app 4 | 5 | ADD ./build/libs/product-catalog-svc-0.0.1.jar /app/app.jar 6 | 7 | EXPOSE 8080 8 | ENTRYPOINT ["java", "-jar", "app.jar"] -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/domain/model/ProductStatus.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.domain.model; 2 | 3 | public enum ProductStatus { 4 | DRAFT,PUBLISHED 5 | } 6 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/deployments/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: product-catalog 5 | spec: 6 | type: ClusterIP 7 | selector: 8 | app: product-catalog 9 | ports: 10 | - port: 80 11 | targetPort: 8080 -------------------------------------------------------------------------------- /product-catalog-svc/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/common/error/InvalidOperationException.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.common.error; 2 | 3 | public class InvalidOperationException extends RuntimeException { 4 | public InvalidOperationException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/common/error/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.common.error; 2 | 3 | public class ResourceNotFoundException extends RuntimeException { 4 | public ResourceNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/query/model/QueryParameter.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.query.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Setter 7 | @Getter 8 | public class QueryParameter { 9 | private String status; 10 | } 11 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/common/error/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.common.error; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Setter 8 | @Getter 9 | @AllArgsConstructor 10 | public class ErrorResponse { 11 | private String message; 12 | private String code; 13 | } 14 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/resource/model/ProductResponse.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.resource.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | @Setter 9 | @Getter 10 | @ToString 11 | @AllArgsConstructor 12 | public class ProductResponse { 13 | private Long productId; 14 | } 15 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/deployments/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: app-ingress 5 | spec: 6 | rules: 7 | - http: 8 | paths: 9 | - path: /products 10 | pathType: Prefix 11 | backend: 12 | service: 13 | name: product-catalog 14 | port: 15 | number: 80 -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/command/ProductCatalogCommand.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.command; 2 | 3 | import dev.techdozo.product.application.domain.model.Product; 4 | 5 | public interface ProductCatalogCommand { 6 | Long addProduct(Product product); 7 | 8 | void deleteProduct(Long productId); 9 | 10 | void publish(Long productId); 11 | 12 | Long updateProduct(Product product); 13 | } 14 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "product-catalog.serviceAccountName" . }} 6 | labels: 7 | {{- include "product-catalog.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/ProductCatalogSvcApplication.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ProductCatalogSvcApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ProductCatalogSvcApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/query/ProductCatalogQuery.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.query; 2 | 3 | import dev.techdozo.product.application.domain.model.Product; 4 | import dev.techdozo.product.application.query.model.QueryParameter; 5 | 6 | import java.util.List; 7 | 8 | public interface ProductCatalogQuery { 9 | Product getProduct(Long productId); 10 | List getProducts(QueryParameter queryParameter); 11 | } 12 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "product-catalog.fullname" . }} 5 | labels: 6 | {{- include "product-catalog.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "product-catalog.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/domain/model/Product.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.domain.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | @Setter 8 | @Getter 9 | @ToString 10 | public class Product { 11 | private Long productId; 12 | private String productStatus; 13 | private String productName; 14 | private String description; 15 | private String price; 16 | private String model; 17 | private String brand; 18 | } 19 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/resource/model/GetProductResponse.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.resource.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | @Setter 8 | @Getter 9 | @ToString 10 | public class GetProductResponse { 11 | private Long productId; 12 | private String productStatus; 13 | private String productName; 14 | private String description; 15 | private String price; 16 | private String model; 17 | private String brand; 18 | } 19 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/deployments/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: product-catalog 5 | labels: 6 | app: product-catalog 7 | spec: 8 | replicas: 3 9 | selector: 10 | matchLabels: 11 | app: product-catalog 12 | template: 13 | metadata: 14 | labels: 15 | app: product-catalog 16 | spec: 17 | containers: 18 | - name: product-catalog 19 | imagePullPolicy: IfNotPresent 20 | image: techdozo/product-catalog-svc:1.0.0 -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "product-catalog.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "product-catalog.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "product-catalog.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/kind/kind-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kind.x-k8s.io/v1alpha4 2 | kind: Cluster 3 | nodes: 4 | - role: control-plane 5 | kubeadmConfigPatches: 6 | - | 7 | kind: InitConfiguration 8 | nodeRegistration: 9 | kubeletExtraArgs: 10 | node-labels: "ingress-ready=true" 11 | extraPortMappings: 12 | - containerPort: 80 13 | hostPort: 81 14 | protocol: TCP 15 | - containerPort: 443 16 | hostPort: 443 17 | protocol: TCP 18 | - role: worker 19 | - role: worker -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/domain/repository/ProductCatalogRepository.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.domain.repository; 2 | 3 | import dev.techdozo.product.application.domain.model.Product; 4 | import dev.techdozo.product.application.query.model.QueryParameter; 5 | 6 | import java.util.List; 7 | 8 | /** Domain repository for the product catalog. */ 9 | public interface ProductCatalogRepository { 10 | Long save(Product product); 11 | 12 | Product get(Long productId); 13 | 14 | List getAll(QueryParameter queryParameter); 15 | 16 | void delete(Long productId); 17 | } 18 | -------------------------------------------------------------------------------- /product-catalog-svc/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | generated 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/persistence/repository/jpa/ProductCatalogJpaRepository.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.persistence.repository.jpa; 2 | 3 | import dev.techdozo.product.persistence.repository.model.ProductPersistable; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.List; 8 | 9 | public interface ProductCatalogJpaRepository extends JpaRepository { 10 | @Query("SELECT u FROM ProductPersistable u WHERE u.productStatus = ?1") 11 | List findAll(String status); 12 | } 13 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/factory/ProductCatalogFactory.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.factory; 2 | 3 | import dev.techdozo.product.application.command.ProductCatalogCommand; 4 | import dev.techdozo.product.application.query.ProductCatalogQuery; 5 | 6 | public interface ProductCatalogFactory { 7 | 8 | /** 9 | * returns product catalog command 10 | * 11 | * @return ProductCatalogCommand 12 | */ 13 | ProductCatalogCommand getProductCatalogCommand(); 14 | 15 | /** 16 | * returns product catalog query 17 | * 18 | * @return query 19 | */ 20 | ProductCatalogQuery getProductQuery(); 21 | } 22 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/persistence/repository/model/ProductPersistable.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.persistence.repository.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | import javax.persistence.*; 8 | 9 | @ToString 10 | @Setter 11 | @Getter 12 | @Entity 13 | @Table(name = "product_catalog") 14 | public class ProductPersistable { 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.AUTO) 17 | private Long id; 18 | 19 | private String productStatus; 20 | private String productName; 21 | private String description; 22 | private String price; 23 | private String model; 24 | private String brand; 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot RESTFul microservices 2 | You can check the full article at [RESTful Microservices with Spring Boot and Kubernetes](https://techdozo.dev/restful-microservices-with-spring-boot-and-kubernetes/) 3 | 4 | ##Overview 5 | This repo consists of following artifacts 6 | - OpenApi spec for product catalog service. 7 | - API implementation `ProductCatalogController` 8 | - Spring Data JPA implemenation of repositories. 9 | - A basic error handling. 10 | 11 | ### Package Structure 12 | 13 | - **resource**: API implementation 14 | - **application**: core business logic 15 | - **persistence**: spring data JPA implemenation of repository classes 16 | - **common**: error handling and spring bean configuration 17 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/resource/mapper/ProductRequestMapper.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.resource.mapper; 2 | 3 | import dev.techdozo.product.application.domain.model.Product; 4 | import dev.techdozo.product.resource.model.ProductRequest; 5 | import dev.techdozo.product.resource.model.GetProductResponse; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.ReportingPolicy; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) 11 | public abstract class ProductRequestMapper { 12 | 13 | public static final ProductRequestMapper MAPPER = Mappers.getMapper(ProductRequestMapper.class); 14 | 15 | public abstract Product map(ProductRequest request); 16 | 17 | public abstract GetProductResponse map(Product product); 18 | } 19 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/resource/model/ProductRequest.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.resource.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Setter 11 | @Getter 12 | @ToString 13 | public class ProductRequest { 14 | private Long productId; 15 | 16 | @NotNull(message = "Product name is required.") 17 | @NotBlank(message = "Product name cannot be blank.") 18 | private String productName; 19 | 20 | private String description; 21 | 22 | @NotNull(message = "Price is required.") 23 | @NotBlank(message = "Price cannot be blank.") 24 | private String price; 25 | 26 | @NotNull(message = "Model is required.") 27 | @NotBlank(message = "Model cannot be blank.") 28 | private String model; 29 | 30 | private String brand; 31 | } 32 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/persistence/mapper/ProductPersistableMapper.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.persistence.mapper; 2 | 3 | import dev.techdozo.product.application.domain.model.Product; 4 | import dev.techdozo.product.persistence.repository.model.ProductPersistable; 5 | import org.mapstruct.Mapper; 6 | import org.mapstruct.Mapping; 7 | import org.mapstruct.ReportingPolicy; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper( 11 | unmappedTargetPolicy = ReportingPolicy.IGNORE) 12 | public abstract class ProductPersistableMapper { 13 | 14 | public static final ProductPersistableMapper MAPPER = 15 | Mappers.getMapper(ProductPersistableMapper.class); 16 | 17 | @Mapping(source = "productId", target = "id") 18 | public abstract ProductPersistable map(Product product); 19 | 20 | @Mapping(source = "id", target = "productId") 21 | public abstract Product map(ProductPersistable productPersistable); 22 | } 23 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "product-catalog.fullname" . }} 6 | labels: 7 | {{- include "product-catalog.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "product-catalog.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/factory/impl/ProductCatalogFactoryImpl.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.factory.impl; 2 | 3 | import dev.techdozo.product.application.command.ProductCatalogCommand; 4 | import dev.techdozo.product.application.factory.ProductCatalogFactory; 5 | import dev.techdozo.product.application.query.ProductCatalogQuery; 6 | 7 | public class ProductCatalogFactoryImpl implements ProductCatalogFactory { 8 | 9 | private final ProductCatalogCommand productCatalogCommand; 10 | private final ProductCatalogQuery productCatalogQuery; 11 | 12 | public ProductCatalogFactoryImpl( 13 | ProductCatalogCommand productCatalogCommand, ProductCatalogQuery productCatalogQuery) { 14 | this.productCatalogCommand = productCatalogCommand; 15 | this.productCatalogQuery = productCatalogQuery; 16 | } 17 | 18 | @Override 19 | public ProductCatalogCommand getProductCatalogCommand() { 20 | return productCatalogCommand; 21 | } 22 | 23 | @Override 24 | public ProductCatalogQuery getProductQuery() { 25 | return productCatalogQuery; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /product-catalog-svc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.5.3' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | } 6 | 7 | group = 'com.techdozo' 8 | version = '0.0.1' 9 | sourceCompatibility = '11' 10 | 11 | configurations { 12 | compileOnly { 13 | extendsFrom annotationProcessor 14 | } 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | def mapstructVersion = '1.4.2.Final' 21 | dependencies { 22 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 23 | implementation 'org.springframework.boot:spring-boot-starter-web' 24 | implementation 'org.hibernate:hibernate-validator:6.1.5.Final' 25 | compileOnly 'org.projectlombok:lombok' 26 | annotationProcessor "org.projectlombok:lombok" 27 | implementation "org.mapstruct:mapstruct:${mapstructVersion}" 28 | annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" 29 | runtimeOnly 'com.h2database:h2' 30 | annotationProcessor 'org.projectlombok:lombok' 31 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 32 | } 33 | 34 | test { 35 | useJUnitPlatform() 36 | } 37 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: product-catalog 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/query/impl/ProductCatalogQueryImpl.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.query.impl; 2 | 3 | import dev.techdozo.product.application.domain.model.Product; 4 | import dev.techdozo.product.application.domain.model.ProductCatalog; 5 | import dev.techdozo.product.application.domain.repository.ProductCatalogRepository; 6 | import dev.techdozo.product.application.query.ProductCatalogQuery; 7 | import dev.techdozo.product.application.query.model.QueryParameter; 8 | 9 | import java.util.List; 10 | 11 | public class ProductCatalogQueryImpl implements ProductCatalogQuery { 12 | private final ProductCatalogRepository productCatalogRepository; 13 | 14 | public ProductCatalogQueryImpl(ProductCatalogRepository productCatalogRepository) { 15 | this.productCatalogRepository = productCatalogRepository; 16 | } 17 | 18 | @Override 19 | public Product getProduct(Long productId) { 20 | var productCatalog = new ProductCatalog(productCatalogRepository); 21 | return productCatalog.get(productId); 22 | } 23 | 24 | @Override 25 | public List getProducts(QueryParameter queryParameter) { 26 | var productCatalog = new ProductCatalog(productCatalogRepository); 27 | return productCatalog.getAll(queryParameter); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/common/error/handler/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.common.error.handler; 2 | 3 | import dev.techdozo.product.common.error.ErrorResponse; 4 | import dev.techdozo.product.common.error.InvalidOperationException; 5 | import dev.techdozo.product.common.error.ResourceNotFoundException; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.ControllerAdvice; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | 11 | @ControllerAdvice 12 | public class GlobalExceptionHandler { 13 | 14 | @ExceptionHandler(ResourceNotFoundException.class) 15 | public ResponseEntity handleException(ResourceNotFoundException cause) { 16 | return new ResponseEntity<>( 17 | new ErrorResponse(cause.getMessage(), "ResourceNotFound"), HttpStatus.NOT_FOUND); 18 | } 19 | 20 | @ExceptionHandler(InvalidOperationException.class) 21 | public ResponseEntity handleException(InvalidOperationException cause) { 22 | return new ResponseEntity<>( 23 | new ErrorResponse(cause.getMessage(), "InvalidOperation"), HttpStatus.PRECONDITION_FAILED); 24 | } 25 | 26 | @ExceptionHandler(RuntimeException.class) 27 | public ResponseEntity handleException(RuntimeException cause) { 28 | return new ResponseEntity<>( 29 | new ErrorResponse(cause.getMessage(), "RuntimeException"), 30 | HttpStatus.INTERNAL_SERVER_ERROR); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/command/impl/ProductCatalogCommandImpl.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.command.impl; 2 | 3 | import dev.techdozo.product.application.command.ProductCatalogCommand; 4 | import dev.techdozo.product.application.domain.model.Product; 5 | import dev.techdozo.product.application.domain.model.ProductCatalog; 6 | import dev.techdozo.product.application.domain.repository.ProductCatalogRepository; 7 | 8 | public class ProductCatalogCommandImpl implements ProductCatalogCommand { 9 | private final ProductCatalogRepository productCatalogRepository; 10 | 11 | public ProductCatalogCommandImpl(ProductCatalogRepository productCatalogRepository) { 12 | this.productCatalogRepository = productCatalogRepository; 13 | } 14 | 15 | @Override 16 | public Long addProduct(Product product) { 17 | var productCatalog = new ProductCatalog(productCatalogRepository); 18 | return productCatalog.addProduct(product); 19 | } 20 | 21 | @Override 22 | public void deleteProduct(Long productId) { 23 | var productCatalog = new ProductCatalog(productCatalogRepository); 24 | productCatalog.deleteProduct(productId); 25 | } 26 | 27 | @Override 28 | public void publish(Long productId) { 29 | var productCatalog = new ProductCatalog(productCatalogRepository); 30 | productCatalog.publish(productId); 31 | 32 | } 33 | 34 | @Override 35 | public Long updateProduct(Product product) { 36 | var productCatalog = new ProductCatalog(productCatalogRepository); 37 | return productCatalog.updateProduct(product); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/common/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.common.config; 2 | 3 | import dev.techdozo.product.application.command.ProductCatalogCommand; 4 | import dev.techdozo.product.application.command.impl.ProductCatalogCommandImpl; 5 | import dev.techdozo.product.application.domain.repository.ProductCatalogRepository; 6 | import dev.techdozo.product.application.factory.ProductCatalogFactory; 7 | import dev.techdozo.product.application.factory.impl.ProductCatalogFactoryImpl; 8 | import dev.techdozo.product.application.query.ProductCatalogQuery; 9 | import dev.techdozo.product.application.query.impl.ProductCatalogQueryImpl; 10 | import dev.techdozo.product.persistence.repository.ProductCatalogRepositoryImpl; 11 | import dev.techdozo.product.persistence.repository.jpa.ProductCatalogJpaRepository; 12 | import lombok.Setter; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | 17 | @Configuration 18 | @Setter 19 | public class AppConfig { 20 | 21 | @Autowired private ProductCatalogJpaRepository productCatalogJpaRepository; 22 | 23 | @Bean 24 | public ProductCatalogFactory productCatalogFactory() { 25 | return new ProductCatalogFactoryImpl(productCatalogCommand(), productCatalogQuery()); 26 | } 27 | 28 | private ProductCatalogQuery productCatalogQuery() { 29 | return new ProductCatalogQueryImpl(productCatalogRepository()); 30 | } 31 | 32 | @Bean 33 | public ProductCatalogCommand productCatalogCommand() { 34 | return new ProductCatalogCommandImpl(productCatalogRepository()); 35 | } 36 | 37 | private ProductCatalogRepository productCatalogRepository() { 38 | return new ProductCatalogRepositoryImpl(productCatalogJpaRepository); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "product-catalog.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "product-catalog.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "product-catalog.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "product-catalog.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "product-catalog.fullname" . }} 5 | labels: 6 | {{- include "product-catalog.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "product-catalog.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "product-catalog.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "product-catalog.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | ports: 37 | - name: http 38 | containerPort: 8080 39 | protocol: TCP 40 | resources: 41 | {{- toYaml .Values.resources | nindent 12 }} 42 | {{- with .Values.nodeSelector }} 43 | nodeSelector: 44 | {{- toYaml . | nindent 8 }} 45 | {{- end }} 46 | {{- with .Values.affinity }} 47 | affinity: 48 | {{- toYaml . | nindent 8 }} 49 | {{- end }} 50 | {{- with .Values.tolerations }} 51 | tolerations: 52 | {{- toYaml . | nindent 8 }} 53 | {{- end }} 54 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/application/domain/model/ProductCatalog.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.application.domain.model; 2 | 3 | import dev.techdozo.product.application.domain.repository.ProductCatalogRepository; 4 | import dev.techdozo.product.application.query.model.QueryParameter; 5 | import dev.techdozo.product.common.error.InvalidOperationException; 6 | import lombok.AllArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @AllArgsConstructor 11 | public class ProductCatalog { 12 | 13 | private final ProductCatalogRepository productCatalogRepository; 14 | 15 | public Long addProduct(Product product) { 16 | // Add business validation 17 | product.setProductStatus(ProductStatus.DRAFT.name()); 18 | return productCatalogRepository.save(product); 19 | } 20 | 21 | public Long updateProduct(Product product) { 22 | var productStatus = product.getProductStatus(); 23 | if (productStatus != null && productStatus.equalsIgnoreCase(ProductStatus.PUBLISHED.name())) { 24 | throw new InvalidOperationException("Product status cannot be changed"); 25 | } 26 | product.setProductStatus(ProductStatus.DRAFT.name()); 27 | return productCatalogRepository.save(product); 28 | } 29 | 30 | public void deleteProduct(Long productId) { 31 | productCatalogRepository.delete(productId); 32 | } 33 | 34 | public List getAll(QueryParameter queryParameter) { 35 | return productCatalogRepository.getAll(queryParameter); 36 | } 37 | 38 | public Product get(Long productId) { 39 | return productCatalogRepository.get(productId); 40 | } 41 | 42 | public void publish(Long productId) { 43 | var product = productCatalogRepository.get(productId); 44 | if (!product.getProductStatus().equalsIgnoreCase(ProductStatus.DRAFT.name())) { 45 | throw new InvalidOperationException("Only DRAFT product can be published"); 46 | } 47 | product.setProductStatus(ProductStatus.PUBLISHED.name()); 48 | productCatalogRepository.save(product); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "product-catalog.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "product-catalog.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "product-catalog.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "product-catalog.labels" -}} 37 | helm.sh/chart: {{ include "product-catalog.chart" . }} 38 | {{ include "product-catalog.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "product-catalog.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "product-catalog.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "product-catalog.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "product-catalog.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for product-catalog. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 3 6 | 7 | image: 8 | repository: techdozo/product-catalog-svc 9 | pullPolicy: IfNotPresent 10 | tag: 1.0.0 11 | 12 | imagePullSecrets: [] 13 | nameOverride: "" 14 | fullnameOverride: "" 15 | 16 | serviceAccount: 17 | # Specifies whether a service account should be created 18 | create: true 19 | # Annotations to add to the service account 20 | annotations: {} 21 | # The name of the service account to use. 22 | # If not set and create is true, a name is generated using the fullname template 23 | name: "" 24 | 25 | podAnnotations: {} 26 | 27 | podSecurityContext: {} 28 | # fsGroup: 2000 29 | 30 | securityContext: {} 31 | # capabilities: 32 | # drop: 33 | # - ALL 34 | # readOnlyRootFilesystem: true 35 | # runAsNonRoot: true 36 | # runAsUser: 1000 37 | 38 | service: 39 | type: ClusterIP 40 | port: 80 41 | 42 | ingress: 43 | enabled: true 44 | className: "" 45 | annotations: {} 46 | # kubernetes.io/ingress.class: nginx 47 | # kubernetes.io/tls-acme: "true" 48 | hosts: 49 | - paths: 50 | - path: /products 51 | pathType: Prefix 52 | tls: [] 53 | # - secretName: chart-example-tls 54 | # hosts: 55 | # - chart-example.local 56 | 57 | resources: {} 58 | # We usually recommend not to specify default resources and to leave this as a conscious 59 | # choice for the user. This also increases chances charts run on environments with little 60 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 61 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 62 | # limits: 63 | # cpu: 100m 64 | # memory: 128Mi 65 | # requests: 66 | # cpu: 100m 67 | # memory: 128Mi 68 | 69 | autoscaling: 70 | enabled: false 71 | minReplicas: 1 72 | maxReplicas: 100 73 | targetCPUUtilizationPercentage: 80 74 | # targetMemoryUtilizationPercentage: 80 75 | 76 | nodeSelector: {} 77 | 78 | tolerations: [] 79 | 80 | affinity: {} 81 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/persistence/repository/ProductCatalogRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.persistence.repository; 2 | 3 | import dev.techdozo.product.application.domain.model.Product; 4 | import dev.techdozo.product.application.domain.repository.ProductCatalogRepository; 5 | import dev.techdozo.product.application.query.model.QueryParameter; 6 | import dev.techdozo.product.common.error.ResourceNotFoundException; 7 | import dev.techdozo.product.persistence.mapper.ProductPersistableMapper; 8 | import dev.techdozo.product.persistence.repository.jpa.ProductCatalogJpaRepository; 9 | import dev.techdozo.product.persistence.repository.model.ProductPersistable; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.stream.Collectors; 14 | 15 | public class ProductCatalogRepositoryImpl implements ProductCatalogRepository { 16 | 17 | private final ProductCatalogJpaRepository productCatalogJpaRepository; 18 | 19 | public ProductCatalogRepositoryImpl(ProductCatalogJpaRepository productCatalogJpaRepository) { 20 | this.productCatalogJpaRepository = productCatalogJpaRepository; 21 | } 22 | 23 | @Override 24 | public Long save(Product product) { 25 | var entity = ProductPersistableMapper.MAPPER.map(product); 26 | entity = productCatalogJpaRepository.save(entity); 27 | return entity.getId(); 28 | } 29 | 30 | @Override 31 | public Product get(Long productId) { 32 | Optional productPersistable = 33 | productCatalogJpaRepository.findById(productId); 34 | return productPersistable 35 | .map(ProductPersistableMapper.MAPPER::map) 36 | .orElseThrow(() -> new ResourceNotFoundException("Product not found")); 37 | } 38 | 39 | @Override 40 | public List getAll(QueryParameter queryParameter) { 41 | var productPersistables = productCatalogJpaRepository.findAll(queryParameter.getStatus()); 42 | return productPersistables.stream() 43 | .map(ProductPersistableMapper.MAPPER::map) 44 | .collect(Collectors.toList()); 45 | } 46 | 47 | @Override 48 | public void delete(Long productId) { 49 | productCatalogJpaRepository.deleteById(productId); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /product-catalog-svc/kubernetes/helm/product-catalog/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "product-catalog.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "product-catalog.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - http: 42 | paths: 43 | {{- range .paths }} 44 | - path: {{ .path }} 45 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 46 | pathType: {{ .pathType }} 47 | {{- end }} 48 | backend: 49 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 50 | service: 51 | name: {{ $fullName }} 52 | port: 53 | number: {{ $svcPort }} 54 | {{- else }} 55 | serviceName: {{ $fullName }} 56 | servicePort: {{ $svcPort }} 57 | {{- end }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | -------------------------------------------------------------------------------- /product-catalog-svc/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /product-catalog-svc/src/main/java/dev/techdozo/product/resource/ProductCatalogController.java: -------------------------------------------------------------------------------- 1 | package dev.techdozo.product.resource; 2 | 3 | import dev.techdozo.product.application.factory.ProductCatalogFactory; 4 | import dev.techdozo.product.application.query.model.QueryParameter; 5 | import dev.techdozo.product.resource.mapper.ProductRequestMapper; 6 | import dev.techdozo.product.resource.model.GetProductResponse; 7 | import dev.techdozo.product.resource.model.ProductRequest; 8 | import dev.techdozo.product.resource.model.ProductResponse; 9 | import lombok.NonNull; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import javax.validation.Valid; 17 | import java.util.List; 18 | import java.util.stream.Collectors; 19 | 20 | @Slf4j 21 | @RestController 22 | public class ProductCatalogController { 23 | 24 | @Autowired private ProductCatalogFactory productCatalogFactory; 25 | 26 | @PostMapping("/products") 27 | public ResponseEntity createProduct( 28 | @RequestHeader("sellerId") String sellerId, 29 | @Valid @RequestBody ProductRequest productRequest) { 30 | 31 | log.info("Creating product, name {}", productRequest.getProductName()); 32 | 33 | var product = ProductRequestMapper.MAPPER.map(productRequest); 34 | var productCatalogCommand = productCatalogFactory.getProductCatalogCommand(); 35 | 36 | var productId = productCatalogCommand.addProduct(product); 37 | 38 | var productResponse = new ProductResponse(productId); 39 | return new ResponseEntity<>(productResponse, HttpStatus.CREATED); 40 | } 41 | 42 | @PutMapping("/products") 43 | public ResponseEntity updateProduct( 44 | @RequestHeader("sellerId") String sellerId, 45 | @Valid @RequestBody ProductRequest productRequest) { 46 | 47 | log.info("Updating product, name {}", productRequest.getProductName()); 48 | 49 | var product = ProductRequestMapper.MAPPER.map(productRequest); 50 | var productCatalogCommand = productCatalogFactory.getProductCatalogCommand(); 51 | 52 | var productId = productCatalogCommand.updateProduct(product); 53 | 54 | var productResponse = new ProductResponse(productId); 55 | if (productRequest.getProductId() == null) { 56 | return new ResponseEntity<>(productResponse, HttpStatus.CREATED); 57 | } 58 | return new ResponseEntity<>(productResponse, HttpStatus.OK); 59 | } 60 | 61 | @GetMapping("/products/{productId}") 62 | public ResponseEntity getProductById( 63 | @RequestHeader("sellerId") String sellerId, @PathVariable @NonNull Long productId) { 64 | 65 | log.info("Getting product, id {}", productId); 66 | 67 | var productCatalogQuery = productCatalogFactory.getProductQuery(); 68 | 69 | var product = productCatalogQuery.getProduct(productId); 70 | 71 | var productResponse = ProductRequestMapper.MAPPER.map(product); 72 | 73 | return new ResponseEntity<>(productResponse, HttpStatus.OK); 74 | } 75 | 76 | @GetMapping("/products") 77 | public ResponseEntity> listProducts( 78 | @RequestHeader("sellerId") String sellerId, @RequestParam String status) { 79 | 80 | log.info("Getting all products"); 81 | 82 | var productCatalogQuery = productCatalogFactory.getProductQuery(); 83 | 84 | var queryParameter = new QueryParameter(); 85 | queryParameter.setStatus(status); 86 | 87 | var products = productCatalogQuery.getProducts(queryParameter); 88 | 89 | var productResponse = 90 | products.stream().map(ProductRequestMapper.MAPPER::map).collect(Collectors.toList()); 91 | 92 | return new ResponseEntity<>(productResponse, HttpStatus.OK); 93 | } 94 | 95 | @DeleteMapping("/products/{productId}") 96 | public ResponseEntity deleteProduct( 97 | @RequestHeader("sellerId") String sellerId, @PathVariable @NonNull Long productId) { 98 | 99 | log.info("Deleting product, id {}", productId); 100 | 101 | var productCatalogCommand = productCatalogFactory.getProductCatalogCommand(); 102 | 103 | productCatalogCommand.deleteProduct(productId); 104 | 105 | return new ResponseEntity<>(HttpStatus.OK); 106 | } 107 | 108 | @PostMapping("/products/{productId}/publish") 109 | public ResponseEntity publishProduct( 110 | @RequestHeader("sellerId") String sellerId, @PathVariable @NonNull Long productId) { 111 | 112 | log.info("Publishing product, id {}", productId); 113 | 114 | var productCatalogCommand = productCatalogFactory.getProductCatalogCommand(); 115 | 116 | productCatalogCommand.publish(productId); 117 | 118 | return new ResponseEntity<>(HttpStatus.OK); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /product-catalog-svc/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /product-catalog-svc/openApi.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: 3.0.0 3 | info: 4 | title: Product catalog service 5 | description: "The product catalog API supports the creation and management of the product. The API includes the following capabilities and operations:\n\n**Create Product**\n\n**Update Product**\n\n**Delete Product**\n\n**Publish Product**\n\n
\nThe following resource collections are offered by this API:\n\n\n**Products** - Products represents collection of products that is sold by seller. \n\n" 6 | version: 1.0.0 7 | servers: 8 | - url: https://virtserver.swaggerhub.com/techdozo5/product-catalog/1.0.0 9 | description: SwaggerHub API Auto Mocking 10 | - url: localhost:8080 11 | description: local 12 | tags: 13 | - name: Seller 14 | paths: 15 | /products: 16 | get: 17 | tags: 18 | - Seller 19 | summary: Return list of products. 20 | description: | 21 | Return list of products. 22 | operationId: ListProducts 23 | parameters: 24 | - name: sellerId 25 | in: header 26 | description: Id of the seller 27 | required: true 28 | style: simple 29 | explode: false 30 | schema: 31 | type: string 32 | example: SL-1234-68686755 33 | - name: status 34 | in: query 35 | description: Status of the product (DRAFT,PUBLISHED). 36 | required: false 37 | style: form 38 | explode: true 39 | schema: 40 | type: string 41 | example: DRAFT 42 | - name: query 43 | in: query 44 | description: Free text search query 45 | required: false 46 | style: form 47 | explode: true 48 | schema: 49 | type: string 50 | example: Apple 51 | responses: 52 | "200": 53 | description: List of all products based on optional status or free text search query. 54 | content: 55 | application/json: 56 | schema: 57 | $ref: '#/components/schemas/Products' 58 | "401": 59 | description: Not authorized 60 | content: 61 | application/json: 62 | schema: 63 | $ref: '#/components/schemas/ErrorResponse' 64 | "500": 65 | description: Internal server error. 66 | content: 67 | application/json: 68 | schema: 69 | $ref: '#/components/schemas/ErrorResponse' 70 | put: 71 | tags: 72 | - Seller 73 | summary: Update product. 74 | description: | 75 | Update product. 76 | operationId: updateProduct 77 | parameters: 78 | - name: sellerId 79 | in: header 80 | description: Id of the seller 81 | required: true 82 | style: simple 83 | explode: false 84 | schema: 85 | type: string 86 | example: SL-1234-68686755 87 | requestBody: 88 | content: 89 | application/json: 90 | schema: 91 | $ref: '#/components/schemas/Product' 92 | responses: 93 | "200": 94 | description: Product updated successfully. 95 | "201": 96 | description: Product created successfully. 97 | content: 98 | application/json: 99 | schema: 100 | $ref: '#/components/schemas/ProductResponse' 101 | "401": 102 | description: Not authorized. 103 | content: 104 | application/json: 105 | schema: 106 | $ref: '#/components/schemas/ErrorResponse' 107 | "500": 108 | description: Internal server error. 109 | content: 110 | application/json: 111 | schema: 112 | $ref: '#/components/schemas/ErrorResponse' 113 | post: 114 | tags: 115 | - Seller 116 | summary: Create a new product. 117 | operationId: CreateProduct 118 | parameters: 119 | - name: sellerId 120 | in: header 121 | description: Id of the seller. 122 | required: true 123 | style: simple 124 | explode: false 125 | schema: 126 | type: string 127 | example: SL-1234-68686755 128 | requestBody: 129 | $ref: '#/components/requestBodies/Product' 130 | responses: 131 | "201": 132 | description: Product created successfully. 133 | content: 134 | application/json: 135 | schema: 136 | $ref: '#/components/schemas/ProductResponse' 137 | "401": 138 | description: Not authorized. 139 | content: 140 | application/json: 141 | schema: 142 | $ref: '#/components/schemas/ErrorResponse' 143 | "500": 144 | description: Internal server error. 145 | content: 146 | application/json: 147 | schema: 148 | $ref: '#/components/schemas/ErrorResponse' 149 | /products/{productId}: 150 | get: 151 | tags: 152 | - Seller 153 | summary: Retrieve product for a given product Id. 154 | description: | 155 | API to get product for a given product Id. 156 | operationId: getProductByID 157 | parameters: 158 | - name: productId 159 | in: path 160 | description: Id of the product. 161 | required: true 162 | style: simple 163 | explode: false 164 | schema: 165 | type: number 166 | - name: sellerId 167 | in: header 168 | description: Id of the seller. 169 | required: true 170 | style: simple 171 | explode: false 172 | schema: 173 | type: string 174 | example: SL-1234-68686755 175 | responses: 176 | "200": 177 | description: Return the product for a productId. 178 | content: 179 | application/json: 180 | schema: 181 | $ref: '#/components/schemas/Product' 182 | "401": 183 | description: Not authorized 184 | content: 185 | application/json: 186 | schema: 187 | $ref: '#/components/schemas/ErrorResponse' 188 | "404": 189 | description: Product not found. 190 | content: 191 | application/json: 192 | schema: 193 | $ref: '#/components/schemas/ErrorResponse' 194 | "500": 195 | description: Internal server error. 196 | content: 197 | application/json: 198 | schema: 199 | $ref: '#/components/schemas/ErrorResponse' 200 | delete: 201 | tags: 202 | - Seller 203 | summary: Delete product. 204 | description: delete product by id. 205 | operationId: DeleteProduct 206 | parameters: 207 | - name: productId 208 | in: path 209 | description: Id of product to be deleted 210 | required: true 211 | style: simple 212 | explode: false 213 | schema: 214 | type: number 215 | - name: sellerId 216 | in: header 217 | description: Id of the seller 218 | required: true 219 | style: simple 220 | explode: false 221 | schema: 222 | type: string 223 | example: SL-1234-68686755 224 | responses: 225 | "200": 226 | description: Successfully deleted product. 227 | content: 228 | application/json: 229 | schema: 230 | $ref: '#/components/schemas/ErrorResponse' 231 | "401": 232 | description: Not authorized 233 | content: 234 | application/json: 235 | schema: 236 | $ref: '#/components/schemas/ErrorResponse' 237 | "404": 238 | description: Product not found. 239 | content: 240 | application/json: 241 | schema: 242 | $ref: '#/components/schemas/ErrorResponse' 243 | "500": 244 | description: Internal server error. 245 | content: 246 | application/json: 247 | schema: 248 | $ref: '#/components/schemas/ErrorResponse' 249 | /products/{productId}/publish: 250 | post: 251 | tags: 252 | - Seller 253 | summary: Publish product 254 | description: Publish product. After products are published it can be bought by buyer. 255 | operationId: PublishProduct 256 | parameters: 257 | - name: productId 258 | in: path 259 | description: ID of product. 260 | required: true 261 | style: simple 262 | explode: false 263 | schema: 264 | type: number 265 | - name: sellerId 266 | in: header 267 | description: Id of the seller 268 | required: true 269 | style: simple 270 | explode: false 271 | schema: 272 | type: string 273 | example: SL-1234-68686755 274 | responses: 275 | "200": 276 | description: Product published. 277 | "401": 278 | description: Not authorized 279 | content: 280 | application/json: 281 | schema: 282 | $ref: '#/components/schemas/ErrorResponse' 283 | "404": 284 | description: Resource not found. 285 | content: 286 | application/json: 287 | schema: 288 | $ref: '#/components/schemas/ErrorResponse' 289 | "500": 290 | description: Internal server error. 291 | content: 292 | application/json: 293 | schema: 294 | $ref: '#/components/schemas/ErrorResponse' 295 | components: 296 | schemas: 297 | Products: 298 | properties: 299 | results: 300 | type: array 301 | items: 302 | $ref: '#/components/schemas/Product' 303 | Product: 304 | type: object 305 | properties: 306 | productId: 307 | type: number 308 | description: Unique identifier of the product 309 | example: 867856 310 | productName: 311 | type: string 312 | description: name of the product 313 | example: Apple iPhone 12 (64GB) 314 | productStatus: 315 | type: string 316 | description: status of product 317 | example: Draft 318 | enum: 319 | - Draft 320 | - Published 321 | description: 322 | type: string 323 | description: description about the product 324 | example: 6.1-inch (15.5 cm diagonal) Super Retina XDR display 325 | price: 326 | type: string 327 | description: price of the product 328 | example: 100$ 329 | model: 330 | type: string 331 | description: model of the product 332 | example: IPhone 12 64GB (PRODUCT)RED 333 | brand: 334 | type: string 335 | description: brand of the product 336 | example: Apple 337 | ProductRequest: 338 | type: object 339 | properties: 340 | productName: 341 | type: string 342 | description: name of the product 343 | example: Apple iPhone 12 (64GB) 344 | description: 345 | type: string 346 | description: description about the product 347 | example: 6.1-inch (15.5 cm diagonal) Super Retina XDR display 348 | price: 349 | type: string 350 | description: price of the product 351 | example: 100$ 352 | model: 353 | type: string 354 | description: model of the product 355 | example: IPhone 12 64GB (PRODUCT)RED 356 | brand: 357 | type: string 358 | description: brand of the product 359 | example: Apple 360 | ProductResponse: 361 | properties: 362 | productId: 363 | type: number 364 | description: Unique identifier of the product 365 | example: 68656 366 | ErrorResponse: 367 | required: 368 | - error 369 | type: object 370 | properties: 371 | message: 372 | type: string 373 | example: error occurred 374 | requestBodies: 375 | Product: 376 | content: 377 | application/json: 378 | schema: 379 | $ref: '#/components/schemas/ProductRequest' 380 | -------------------------------------------------------------------------------- /product-catalog-svc/collections/Product catalog service.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "fd16b91c-9b39-4437-ba98-6642f3eed393", 4 | "name": "Product catalog service", 5 | "description": "The product catalog API supports the creation and management of the product. The API includes the following capabilities and operations:\n\n**Create Product**\n\n**Update Product**\n\n**Delete Product**\n\n**Publish Product**\n\n
\nThe following resource collections are offered by this API:\n\n\n**Products** - Products represents collection of products that is sold by seller. \n\n", 6 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 7 | }, 8 | "item": [ 9 | { 10 | "name": "products", 11 | "item": [ 12 | { 13 | "name": "{product Id}", 14 | "item": [ 15 | { 16 | "name": "Retrieve product for a given product Id.", 17 | "event": [ 18 | { 19 | "listen": "prerequest", 20 | "script": { 21 | "exec": [ 22 | "pm.collectionVariables.get(\"productId\");" 23 | ], 24 | "type": "text/javascript" 25 | } 26 | } 27 | ], 28 | "request": { 29 | "method": "GET", 30 | "header": [ 31 | { 32 | "description": "(Required) Id of the seller.", 33 | "key": "sellerId", 34 | "value": "SL-1234-68686755" 35 | } 36 | ], 37 | "url": { 38 | "raw": "{{baseUrl}}/products/{{productId}}", 39 | "host": [ 40 | "{{baseUrl}}" 41 | ], 42 | "path": [ 43 | "products", 44 | "{{productId}}" 45 | ] 46 | }, 47 | "description": "API to get product for a given product Id.\n" 48 | }, 49 | "response": [ 50 | { 51 | "name": "Return the product for a productId.", 52 | "originalRequest": { 53 | "method": "GET", 54 | "header": [ 55 | { 56 | "description": "(Required) Id of the seller.", 57 | "key": "sellerId", 58 | "value": "SL-1234-68686755" 59 | } 60 | ], 61 | "url": { 62 | "raw": "{{baseUrl}}/products/:productId", 63 | "host": [ 64 | "{{baseUrl}}" 65 | ], 66 | "path": [ 67 | "products", 68 | ":productId" 69 | ], 70 | "variable": [ 71 | { 72 | "key": "productId" 73 | } 74 | ] 75 | } 76 | }, 77 | "status": "OK", 78 | "code": 200, 79 | "_postman_previewlanguage": "json", 80 | "header": [ 81 | { 82 | "key": "Content-Type", 83 | "value": "application/json" 84 | } 85 | ], 86 | "cookie": [], 87 | "body": "{\n \"productId\": 867856,\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"productStatus\": \"Draft\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n}" 88 | }, 89 | { 90 | "name": "Not authorized", 91 | "originalRequest": { 92 | "method": "GET", 93 | "header": [ 94 | { 95 | "description": "(Required) Id of the seller.", 96 | "key": "sellerId", 97 | "value": "SL-1234-68686755" 98 | } 99 | ], 100 | "url": { 101 | "raw": "{{baseUrl}}/products/:productId", 102 | "host": [ 103 | "{{baseUrl}}" 104 | ], 105 | "path": [ 106 | "products", 107 | ":productId" 108 | ], 109 | "variable": [ 110 | { 111 | "key": "productId" 112 | } 113 | ] 114 | } 115 | }, 116 | "status": "Unauthorized", 117 | "code": 401, 118 | "_postman_previewlanguage": "json", 119 | "header": [ 120 | { 121 | "key": "Content-Type", 122 | "value": "application/json" 123 | } 124 | ], 125 | "cookie": [], 126 | "body": "{\n \"message\": \"error occurred\"\n}" 127 | }, 128 | { 129 | "name": "Product not found.", 130 | "originalRequest": { 131 | "method": "GET", 132 | "header": [ 133 | { 134 | "description": "(Required) Id of the seller.", 135 | "key": "sellerId", 136 | "value": "SL-1234-68686755" 137 | } 138 | ], 139 | "url": { 140 | "raw": "{{baseUrl}}/products/:productId", 141 | "host": [ 142 | "{{baseUrl}}" 143 | ], 144 | "path": [ 145 | "products", 146 | ":productId" 147 | ], 148 | "variable": [ 149 | { 150 | "key": "productId" 151 | } 152 | ] 153 | } 154 | }, 155 | "status": "Not Found", 156 | "code": 404, 157 | "_postman_previewlanguage": "json", 158 | "header": [ 159 | { 160 | "key": "Content-Type", 161 | "value": "application/json" 162 | } 163 | ], 164 | "cookie": [], 165 | "body": "{\n \"message\": \"error occurred\"\n}" 166 | }, 167 | { 168 | "name": "Internal server error.", 169 | "originalRequest": { 170 | "method": "GET", 171 | "header": [ 172 | { 173 | "description": "(Required) Id of the seller.", 174 | "key": "sellerId", 175 | "value": "SL-1234-68686755" 176 | } 177 | ], 178 | "url": { 179 | "raw": "{{baseUrl}}/products/:productId", 180 | "host": [ 181 | "{{baseUrl}}" 182 | ], 183 | "path": [ 184 | "products", 185 | ":productId" 186 | ], 187 | "variable": [ 188 | { 189 | "key": "productId" 190 | } 191 | ] 192 | } 193 | }, 194 | "status": "Internal Server Error", 195 | "code": 500, 196 | "_postman_previewlanguage": "json", 197 | "header": [ 198 | { 199 | "key": "Content-Type", 200 | "value": "application/json" 201 | } 202 | ], 203 | "cookie": [], 204 | "body": "{\n \"message\": \"error occurred\"\n}" 205 | } 206 | ] 207 | }, 208 | { 209 | "name": "Delete product.", 210 | "event": [ 211 | { 212 | "listen": "prerequest", 213 | "script": { 214 | "exec": [ 215 | "pm.collectionVariables.get(\"productId\");" 216 | ], 217 | "type": "text/javascript" 218 | } 219 | } 220 | ], 221 | "request": { 222 | "method": "DELETE", 223 | "header": [ 224 | { 225 | "description": "(Required) Id of the seller", 226 | "key": "sellerId", 227 | "value": "SL-1234-68686755" 228 | } 229 | ], 230 | "url": { 231 | "raw": "{{baseUrl}}/products/{{productId}}", 232 | "host": [ 233 | "{{baseUrl}}" 234 | ], 235 | "path": [ 236 | "products", 237 | "{{productId}}" 238 | ] 239 | }, 240 | "description": "delete product by id." 241 | }, 242 | "response": [ 243 | { 244 | "name": "Successfully deleted product.", 245 | "originalRequest": { 246 | "method": "DELETE", 247 | "header": [ 248 | { 249 | "description": "(Required) Id of the seller", 250 | "key": "sellerId", 251 | "value": "SL-1234-68686755" 252 | } 253 | ], 254 | "url": { 255 | "raw": "{{baseUrl}}/products/:productId", 256 | "host": [ 257 | "{{baseUrl}}" 258 | ], 259 | "path": [ 260 | "products", 261 | ":productId" 262 | ], 263 | "variable": [ 264 | { 265 | "key": "productId" 266 | } 267 | ] 268 | } 269 | }, 270 | "status": "OK", 271 | "code": 200, 272 | "_postman_previewlanguage": "json", 273 | "header": [ 274 | { 275 | "key": "Content-Type", 276 | "value": "application/json" 277 | } 278 | ], 279 | "cookie": [], 280 | "body": "{\n \"message\": \"error occurred\"\n}" 281 | }, 282 | { 283 | "name": "Not authorized", 284 | "originalRequest": { 285 | "method": "DELETE", 286 | "header": [ 287 | { 288 | "description": "(Required) Id of the seller", 289 | "key": "sellerId", 290 | "value": "SL-1234-68686755" 291 | } 292 | ], 293 | "url": { 294 | "raw": "{{baseUrl}}/products/:productId", 295 | "host": [ 296 | "{{baseUrl}}" 297 | ], 298 | "path": [ 299 | "products", 300 | ":productId" 301 | ], 302 | "variable": [ 303 | { 304 | "key": "productId" 305 | } 306 | ] 307 | } 308 | }, 309 | "status": "Unauthorized", 310 | "code": 401, 311 | "_postman_previewlanguage": "json", 312 | "header": [ 313 | { 314 | "key": "Content-Type", 315 | "value": "application/json" 316 | } 317 | ], 318 | "cookie": [], 319 | "body": "{\n \"message\": \"error occurred\"\n}" 320 | }, 321 | { 322 | "name": "Product not found.", 323 | "originalRequest": { 324 | "method": "DELETE", 325 | "header": [ 326 | { 327 | "description": "(Required) Id of the seller", 328 | "key": "sellerId", 329 | "value": "SL-1234-68686755" 330 | } 331 | ], 332 | "url": { 333 | "raw": "{{baseUrl}}/products/:productId", 334 | "host": [ 335 | "{{baseUrl}}" 336 | ], 337 | "path": [ 338 | "products", 339 | ":productId" 340 | ], 341 | "variable": [ 342 | { 343 | "key": "productId" 344 | } 345 | ] 346 | } 347 | }, 348 | "status": "Not Found", 349 | "code": 404, 350 | "_postman_previewlanguage": "json", 351 | "header": [ 352 | { 353 | "key": "Content-Type", 354 | "value": "application/json" 355 | } 356 | ], 357 | "cookie": [], 358 | "body": "{\n \"message\": \"error occurred\"\n}" 359 | }, 360 | { 361 | "name": "Internal server error.", 362 | "originalRequest": { 363 | "method": "DELETE", 364 | "header": [ 365 | { 366 | "description": "(Required) Id of the seller", 367 | "key": "sellerId", 368 | "value": "SL-1234-68686755" 369 | } 370 | ], 371 | "url": { 372 | "raw": "{{baseUrl}}/products/:productId", 373 | "host": [ 374 | "{{baseUrl}}" 375 | ], 376 | "path": [ 377 | "products", 378 | ":productId" 379 | ], 380 | "variable": [ 381 | { 382 | "key": "productId" 383 | } 384 | ] 385 | } 386 | }, 387 | "status": "Internal Server Error", 388 | "code": 500, 389 | "_postman_previewlanguage": "json", 390 | "header": [ 391 | { 392 | "key": "Content-Type", 393 | "value": "application/json" 394 | } 395 | ], 396 | "cookie": [], 397 | "body": "{\n \"message\": \"error occurred\"\n}" 398 | } 399 | ] 400 | }, 401 | { 402 | "name": "Publish product", 403 | "event": [ 404 | { 405 | "listen": "prerequest", 406 | "script": { 407 | "exec": [ 408 | "pm.collectionVariables.get(\"productId\");" 409 | ], 410 | "type": "text/javascript" 411 | } 412 | } 413 | ], 414 | "request": { 415 | "method": "POST", 416 | "header": [ 417 | { 418 | "description": "(Required) Id of the seller", 419 | "key": "sellerId", 420 | "value": "SL-1234-68686755" 421 | } 422 | ], 423 | "url": { 424 | "raw": "{{baseUrl}}/products/{{productId}}/publish", 425 | "host": [ 426 | "{{baseUrl}}" 427 | ], 428 | "path": [ 429 | "products", 430 | "{{productId}}", 431 | "publish" 432 | ] 433 | }, 434 | "description": "Publish product. After products are published it can be bought by buyer." 435 | }, 436 | "response": [ 437 | { 438 | "name": "Product published.", 439 | "originalRequest": { 440 | "method": "POST", 441 | "header": [ 442 | { 443 | "description": "(Required) Id of the seller", 444 | "key": "sellerId", 445 | "value": "SL-1234-68686755" 446 | } 447 | ], 448 | "url": { 449 | "raw": "{{baseUrl}}/products/:productId/publish", 450 | "host": [ 451 | "{{baseUrl}}" 452 | ], 453 | "path": [ 454 | "products", 455 | ":productId", 456 | "publish" 457 | ], 458 | "variable": [ 459 | { 460 | "key": "productId" 461 | } 462 | ] 463 | } 464 | }, 465 | "status": "OK", 466 | "code": 200, 467 | "_postman_previewlanguage": "text", 468 | "header": [ 469 | { 470 | "key": "Content-Type", 471 | "value": "text/plain" 472 | } 473 | ], 474 | "cookie": [], 475 | "body": "" 476 | }, 477 | { 478 | "name": "Not authorized", 479 | "originalRequest": { 480 | "method": "POST", 481 | "header": [ 482 | { 483 | "description": "(Required) Id of the seller", 484 | "key": "sellerId", 485 | "value": "SL-1234-68686755" 486 | } 487 | ], 488 | "url": { 489 | "raw": "{{baseUrl}}/products/:productId/publish", 490 | "host": [ 491 | "{{baseUrl}}" 492 | ], 493 | "path": [ 494 | "products", 495 | ":productId", 496 | "publish" 497 | ], 498 | "variable": [ 499 | { 500 | "key": "productId" 501 | } 502 | ] 503 | } 504 | }, 505 | "status": "Unauthorized", 506 | "code": 401, 507 | "_postman_previewlanguage": "json", 508 | "header": [ 509 | { 510 | "key": "Content-Type", 511 | "value": "application/json" 512 | } 513 | ], 514 | "cookie": [], 515 | "body": "{\n \"message\": \"error occurred\"\n}" 516 | }, 517 | { 518 | "name": "Resource not found.", 519 | "originalRequest": { 520 | "method": "POST", 521 | "header": [ 522 | { 523 | "description": "(Required) Id of the seller", 524 | "key": "sellerId", 525 | "value": "SL-1234-68686755" 526 | } 527 | ], 528 | "url": { 529 | "raw": "{{baseUrl}}/products/:productId/publish", 530 | "host": [ 531 | "{{baseUrl}}" 532 | ], 533 | "path": [ 534 | "products", 535 | ":productId", 536 | "publish" 537 | ], 538 | "variable": [ 539 | { 540 | "key": "productId" 541 | } 542 | ] 543 | } 544 | }, 545 | "status": "Not Found", 546 | "code": 404, 547 | "_postman_previewlanguage": "json", 548 | "header": [ 549 | { 550 | "key": "Content-Type", 551 | "value": "application/json" 552 | } 553 | ], 554 | "cookie": [], 555 | "body": "{\n \"message\": \"error occurred\"\n}" 556 | }, 557 | { 558 | "name": "Internal server error.", 559 | "originalRequest": { 560 | "method": "POST", 561 | "header": [ 562 | { 563 | "description": "(Required) Id of the seller", 564 | "key": "sellerId", 565 | "value": "SL-1234-68686755" 566 | } 567 | ], 568 | "url": { 569 | "raw": "{{baseUrl}}/products/:productId/publish", 570 | "host": [ 571 | "{{baseUrl}}" 572 | ], 573 | "path": [ 574 | "products", 575 | ":productId", 576 | "publish" 577 | ], 578 | "variable": [ 579 | { 580 | "key": "productId" 581 | } 582 | ] 583 | } 584 | }, 585 | "status": "Internal Server Error", 586 | "code": 500, 587 | "_postman_previewlanguage": "json", 588 | "header": [ 589 | { 590 | "key": "Content-Type", 591 | "value": "application/json" 592 | } 593 | ], 594 | "cookie": [], 595 | "body": "{\n \"message\": \"error occurred\"\n}" 596 | } 597 | ] 598 | } 599 | ] 600 | }, 601 | { 602 | "name": "Create a new product.", 603 | "event": [ 604 | { 605 | "listen": "test", 606 | "script": { 607 | "exec": [ 608 | "const jsonResponse = pm.response.json();\r", 609 | "pm.collectionVariables.set(\"productId\", jsonResponse.productId);" 610 | ], 611 | "type": "text/javascript" 612 | } 613 | } 614 | ], 615 | "request": { 616 | "method": "POST", 617 | "header": [ 618 | { 619 | "description": "(Required) Id of the seller.", 620 | "key": "sellerId", 621 | "value": "SL-1234-68686755" 622 | }, 623 | { 624 | "key": "Content-Type", 625 | "value": "application/json" 626 | } 627 | ], 628 | "body": { 629 | "mode": "raw", 630 | "raw": "{\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n}" 631 | }, 632 | "url": { 633 | "raw": "{{baseUrl}}/products", 634 | "host": [ 635 | "{{baseUrl}}" 636 | ], 637 | "path": [ 638 | "products" 639 | ] 640 | } 641 | }, 642 | "response": [ 643 | { 644 | "name": "Product created successfully.", 645 | "originalRequest": { 646 | "method": "POST", 647 | "header": [ 648 | { 649 | "description": "(Required) Id of the seller.", 650 | "key": "sellerId", 651 | "value": "SL-1234-68686755" 652 | } 653 | ], 654 | "url": { 655 | "raw": "{{baseUrl}}/products", 656 | "host": [ 657 | "{{baseUrl}}" 658 | ], 659 | "path": [ 660 | "products" 661 | ] 662 | } 663 | }, 664 | "status": "Created", 665 | "code": 201, 666 | "_postman_previewlanguage": "json", 667 | "header": [ 668 | { 669 | "key": "Content-Type", 670 | "value": "application/json" 671 | } 672 | ], 673 | "cookie": [], 674 | "body": "{\n \"productId\": 68656\n}" 675 | }, 676 | { 677 | "name": "Not authorized.", 678 | "originalRequest": { 679 | "method": "POST", 680 | "header": [ 681 | { 682 | "description": "(Required) Id of the seller.", 683 | "key": "sellerId", 684 | "value": "SL-1234-68686755" 685 | } 686 | ], 687 | "url": { 688 | "raw": "{{baseUrl}}/products", 689 | "host": [ 690 | "{{baseUrl}}" 691 | ], 692 | "path": [ 693 | "products" 694 | ] 695 | } 696 | }, 697 | "status": "Unauthorized", 698 | "code": 401, 699 | "_postman_previewlanguage": "json", 700 | "header": [ 701 | { 702 | "key": "Content-Type", 703 | "value": "application/json" 704 | } 705 | ], 706 | "cookie": [], 707 | "body": "{\n \"message\": \"error occurred\"\n}" 708 | }, 709 | { 710 | "name": "Internal server error.", 711 | "originalRequest": { 712 | "method": "POST", 713 | "header": [ 714 | { 715 | "description": "(Required) Id of the seller.", 716 | "key": "sellerId", 717 | "value": "SL-1234-68686755" 718 | } 719 | ], 720 | "url": { 721 | "raw": "{{baseUrl}}/products", 722 | "host": [ 723 | "{{baseUrl}}" 724 | ], 725 | "path": [ 726 | "products" 727 | ] 728 | } 729 | }, 730 | "status": "Internal Server Error", 731 | "code": 500, 732 | "_postman_previewlanguage": "json", 733 | "header": [ 734 | { 735 | "key": "Content-Type", 736 | "value": "application/json" 737 | } 738 | ], 739 | "cookie": [], 740 | "body": "{\n \"message\": \"error occurred\"\n}" 741 | } 742 | ] 743 | }, 744 | { 745 | "name": "Return list of products.", 746 | "request": { 747 | "method": "GET", 748 | "header": [ 749 | { 750 | "description": "(Required) Id of the seller", 751 | "key": "sellerId", 752 | "value": "SL-1234-68686755" 753 | } 754 | ], 755 | "url": { 756 | "raw": "{{baseUrl}}/products?status=DRAFT&query=Apple", 757 | "host": [ 758 | "{{baseUrl}}" 759 | ], 760 | "path": [ 761 | "products" 762 | ], 763 | "query": [ 764 | { 765 | "key": "status", 766 | "value": "DRAFT", 767 | "description": "Status of the product (DRAFT,PUBLISHED)." 768 | }, 769 | { 770 | "key": "query", 771 | "value": "Apple", 772 | "description": "Free text search query" 773 | } 774 | ] 775 | }, 776 | "description": "Return list of products.\n" 777 | }, 778 | "response": [ 779 | { 780 | "name": "List of all products based on optional status or free text search query.", 781 | "originalRequest": { 782 | "method": "GET", 783 | "header": [ 784 | { 785 | "description": "(Required) Id of the seller", 786 | "key": "sellerId", 787 | "value": "SL-1234-68686755" 788 | } 789 | ], 790 | "url": { 791 | "raw": "{{baseUrl}}/products?status=DRAFT&query=Apple", 792 | "host": [ 793 | "{{baseUrl}}" 794 | ], 795 | "path": [ 796 | "products" 797 | ], 798 | "query": [ 799 | { 800 | "key": "status", 801 | "value": "DRAFT" 802 | }, 803 | { 804 | "key": "query", 805 | "value": "Apple" 806 | } 807 | ] 808 | } 809 | }, 810 | "status": "OK", 811 | "code": 200, 812 | "_postman_previewlanguage": "json", 813 | "header": [ 814 | { 815 | "key": "Content-Type", 816 | "value": "application/json" 817 | } 818 | ], 819 | "cookie": [], 820 | "body": "{\n \"results\": [\n {\n \"productId\": 867856,\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"productStatus\": \"Draft\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n },\n {\n \"productId\": 867856,\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"productStatus\": \"Draft\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n }\n ]\n}" 821 | }, 822 | { 823 | "name": "Not authorized", 824 | "originalRequest": { 825 | "method": "GET", 826 | "header": [ 827 | { 828 | "description": "(Required) Id of the seller", 829 | "key": "sellerId", 830 | "value": "SL-1234-68686755" 831 | } 832 | ], 833 | "url": { 834 | "raw": "{{baseUrl}}/products?status=DRAFT&query=Apple", 835 | "host": [ 836 | "{{baseUrl}}" 837 | ], 838 | "path": [ 839 | "products" 840 | ], 841 | "query": [ 842 | { 843 | "key": "status", 844 | "value": "DRAFT" 845 | }, 846 | { 847 | "key": "query", 848 | "value": "Apple" 849 | } 850 | ] 851 | } 852 | }, 853 | "status": "Unauthorized", 854 | "code": 401, 855 | "_postman_previewlanguage": "json", 856 | "header": [ 857 | { 858 | "key": "Content-Type", 859 | "value": "application/json" 860 | } 861 | ], 862 | "cookie": [], 863 | "body": "{\n \"message\": \"error occurred\"\n}" 864 | }, 865 | { 866 | "name": "Internal server error.", 867 | "originalRequest": { 868 | "method": "GET", 869 | "header": [ 870 | { 871 | "description": "(Required) Id of the seller", 872 | "key": "sellerId", 873 | "value": "SL-1234-68686755" 874 | } 875 | ], 876 | "url": { 877 | "raw": "{{baseUrl}}/products?status=DRAFT&query=Apple", 878 | "host": [ 879 | "{{baseUrl}}" 880 | ], 881 | "path": [ 882 | "products" 883 | ], 884 | "query": [ 885 | { 886 | "key": "status", 887 | "value": "DRAFT" 888 | }, 889 | { 890 | "key": "query", 891 | "value": "Apple" 892 | } 893 | ] 894 | } 895 | }, 896 | "status": "Internal Server Error", 897 | "code": 500, 898 | "_postman_previewlanguage": "json", 899 | "header": [ 900 | { 901 | "key": "Content-Type", 902 | "value": "application/json" 903 | } 904 | ], 905 | "cookie": [], 906 | "body": "{\n \"message\": \"error occurred\"\n}" 907 | } 908 | ] 909 | }, 910 | { 911 | "name": "Update product.", 912 | "request": { 913 | "method": "PUT", 914 | "header": [ 915 | { 916 | "description": "(Required) Id of the seller", 917 | "key": "sellerId", 918 | "value": "SL-1234-68686755" 919 | }, 920 | { 921 | "key": "Content-Type", 922 | "value": "application/json" 923 | } 924 | ], 925 | "body": { 926 | "mode": "raw", 927 | "raw": "{\n \"productId\": 867856,\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"productStatus\": \"Draft\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n}" 928 | }, 929 | "url": { 930 | "raw": "{{baseUrl}}/products", 931 | "host": [ 932 | "{{baseUrl}}" 933 | ], 934 | "path": [ 935 | "products" 936 | ] 937 | }, 938 | "description": "Update product.\n" 939 | }, 940 | "response": [ 941 | { 942 | "name": "Product updated successfully.", 943 | "originalRequest": { 944 | "method": "PUT", 945 | "header": [ 946 | { 947 | "description": "(Required) Id of the seller", 948 | "key": "sellerId", 949 | "value": "SL-1234-68686755" 950 | } 951 | ], 952 | "body": { 953 | "mode": "raw", 954 | "raw": "{\n \"productId\": 867856,\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"productStatus\": \"Draft\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n}" 955 | }, 956 | "url": { 957 | "raw": "{{baseUrl}}/products", 958 | "host": [ 959 | "{{baseUrl}}" 960 | ], 961 | "path": [ 962 | "products" 963 | ] 964 | } 965 | }, 966 | "status": "OK", 967 | "code": 200, 968 | "_postman_previewlanguage": "text", 969 | "header": [ 970 | { 971 | "key": "Content-Type", 972 | "value": "text/plain" 973 | } 974 | ], 975 | "cookie": [], 976 | "body": "" 977 | }, 978 | { 979 | "name": "Product created successfully.", 980 | "originalRequest": { 981 | "method": "PUT", 982 | "header": [ 983 | { 984 | "description": "(Required) Id of the seller", 985 | "key": "sellerId", 986 | "value": "SL-1234-68686755" 987 | } 988 | ], 989 | "body": { 990 | "mode": "raw", 991 | "raw": "{\n \"productId\": 867856,\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"productStatus\": \"Draft\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n}" 992 | }, 993 | "url": { 994 | "raw": "{{baseUrl}}/products", 995 | "host": [ 996 | "{{baseUrl}}" 997 | ], 998 | "path": [ 999 | "products" 1000 | ] 1001 | } 1002 | }, 1003 | "status": "Created", 1004 | "code": 201, 1005 | "_postman_previewlanguage": "json", 1006 | "header": [ 1007 | { 1008 | "key": "Content-Type", 1009 | "value": "application/json" 1010 | } 1011 | ], 1012 | "cookie": [], 1013 | "body": "{\n \"productId\": 68656\n}" 1014 | }, 1015 | { 1016 | "name": "Not authorized.", 1017 | "originalRequest": { 1018 | "method": "PUT", 1019 | "header": [ 1020 | { 1021 | "description": "(Required) Id of the seller", 1022 | "key": "sellerId", 1023 | "value": "SL-1234-68686755" 1024 | } 1025 | ], 1026 | "body": { 1027 | "mode": "raw", 1028 | "raw": "{\n \"productId\": 867856,\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"productStatus\": \"Draft\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n}" 1029 | }, 1030 | "url": { 1031 | "raw": "{{baseUrl}}/products", 1032 | "host": [ 1033 | "{{baseUrl}}" 1034 | ], 1035 | "path": [ 1036 | "products" 1037 | ] 1038 | } 1039 | }, 1040 | "status": "Unauthorized", 1041 | "code": 401, 1042 | "_postman_previewlanguage": "json", 1043 | "header": [ 1044 | { 1045 | "key": "Content-Type", 1046 | "value": "application/json" 1047 | } 1048 | ], 1049 | "cookie": [], 1050 | "body": "{\n \"message\": \"error occurred\"\n}" 1051 | }, 1052 | { 1053 | "name": "Internal server error.", 1054 | "originalRequest": { 1055 | "method": "PUT", 1056 | "header": [ 1057 | { 1058 | "description": "(Required) Id of the seller", 1059 | "key": "sellerId", 1060 | "value": "SL-1234-68686755" 1061 | } 1062 | ], 1063 | "body": { 1064 | "mode": "raw", 1065 | "raw": "{\n \"productId\": 867856,\n \"productName\": \"Apple iPhone 12 (64GB)\",\n \"productStatus\": \"Draft\",\n \"description\": \"6.1-inch (15.5 cm diagonal) Super Retina XDR display\",\n \"price\": \"100$\",\n \"model\": \"IPhone 12 64GB (PRODUCT)RED\",\n \"brand\": \"Apple\"\n}" 1066 | }, 1067 | "url": { 1068 | "raw": "{{baseUrl}}/products", 1069 | "host": [ 1070 | "{{baseUrl}}" 1071 | ], 1072 | "path": [ 1073 | "products" 1074 | ] 1075 | } 1076 | }, 1077 | "status": "Internal Server Error", 1078 | "code": 500, 1079 | "_postman_previewlanguage": "json", 1080 | "header": [ 1081 | { 1082 | "key": "Content-Type", 1083 | "value": "application/json" 1084 | } 1085 | ], 1086 | "cookie": [], 1087 | "body": "{\n \"message\": \"error occurred\"\n}" 1088 | } 1089 | ] 1090 | } 1091 | ] 1092 | } 1093 | ], 1094 | "event": [ 1095 | { 1096 | "listen": "prerequest", 1097 | "script": { 1098 | "type": "text/javascript", 1099 | "exec": [ 1100 | "" 1101 | ] 1102 | } 1103 | }, 1104 | { 1105 | "listen": "test", 1106 | "script": { 1107 | "type": "text/javascript", 1108 | "exec": [ 1109 | "" 1110 | ] 1111 | } 1112 | } 1113 | ], 1114 | "variable": [ 1115 | { 1116 | "key": "baseUrl", 1117 | "value": "localhost:8080" 1118 | } 1119 | ] 1120 | } --------------------------------------------------------------------------------