Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/axonivy-market/marketplace
Browse files Browse the repository at this point in the history
… into feature/MARP-994-Market-website-Artifact-and-version-in-download-feature-not-save-update

# Conflicts:
#	marketplace-service/src/main/resources/application.properties
#	marketplace-ui/src/app/app.component.html
#	marketplace-ui/src/app/app.component.scss
#	marketplace-ui/src/app/app.component.ts
#	marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html
#	marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts
#	marketplace-ui/src/app/shared/components/header/search-bar/search-bar.component.spec.ts
  • Loading branch information
linhpd-axonivy committed Sep 20, 2024
2 parents 0ab778a + a55cbb8 commit 30dd68d
Show file tree
Hide file tree
Showing 101 changed files with 1,553 additions and 636 deletions.
11 changes: 9 additions & 2 deletions .github/workflows/ui-ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

steps:
- name: Remove unused sonar images
run: docker image prune -af
- name: Execute Tests
run: |
cd ./marketplace-ui
Expand All @@ -64,3 +62,12 @@ jobs:
env:
SONAR_TOKEN: ${{ env.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }}

clean-up:
name: Remove unused docker images
needs: analysis
runs-on: self-hosted

steps:
- name: Remove unused sonar images
run: docker image prune -af
3 changes: 2 additions & 1 deletion marketplace-build/.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ MARKET_GITHUB_TOKEN=
MARKET_GITHUB_OAUTH_APP_CLIENT_ID=
MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET=
MARKET_JWT_SECRET_KEY=
MARKET_CORS_ALLOWED_ORIGIN=*
MARKET_CORS_ALLOWED_ORIGIN=*
MARKET_MONGO_LOG_LEVEL=DEBUG
3 changes: 2 additions & 1 deletion marketplace-build/dev/.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ MARKET_GITHUB_TOKEN=
MARKET_GITHUB_OAUTH_APP_CLIENT_ID=
MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET=
MARKET_JWT_SECRET_KEY=
MARKET_CORS_ALLOWED_ORIGIN=*
MARKET_CORS_ALLOWED_ORIGIN=*
MARKET_MONGO_LOG_LEVEL=DEBUG
3 changes: 2 additions & 1 deletion marketplace-build/dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ services:
container_name: marketplace-service
restart: always
volumes:
- /home/axonivy/marketplace/data/market-installations.json:/home/data/market-installation.json
- /home/axonivy/marketplace/data/market-installations.json:/data/market-installation.json
environment:
- MONGODB_HOST=${SERVICE_MONGODB_HOST}
- MONGODB_DATABASE=${SERVICE_MONGODB_DATABASE}
Expand All @@ -34,6 +34,7 @@ services:
- MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET=${MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET}
- MARKET_JWT_SECRET_KEY=${MARKET_JWT_SECRET_KEY}
- MARKET_CORS_ALLOWED_ORIGIN=${MARKET_CORS_ALLOWED_ORIGIN}
- MARKET_MONGO_LOG_LEVEL=${MARKET_MONGO_LOG_LEVEL}
build:
context: ../../marketplace-service
dockerfile: Dockerfile
Expand Down
3 changes: 2 additions & 1 deletion marketplace-build/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ services:
container_name: marketplace-service
restart: always
volumes:
- /home/axonivy/marketplace/data/market-installations.json:/home/data/market-installation.json
- /home/axonivy/marketplace/data/market-installations.json:/data/market-installation.json
environment:
- MONGODB_HOST=${SERVICE_MONGODB_HOST}
- MONGODB_DATABASE=${SERVICE_MONGODB_DATABASE}
Expand All @@ -49,6 +49,7 @@ services:
- MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET=${MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET}
- MARKET_JWT_SECRET_KEY=${MARKET_JWT_SECRET_KEY}
- MARKET_CORS_ALLOWED_ORIGIN=${MARKET_CORS_ALLOWED_ORIGIN}
- MARKET_MONGO_LOG_LEVEL=${MARKET_MONGO_LOG_LEVEL}
build:
context: ../marketplace-service
dockerfile: Dockerfile
Expand Down
3 changes: 2 additions & 1 deletion marketplace-build/release/.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ MARKET_GITHUB_TOKEN=
MARKET_GITHUB_OAUTH_APP_CLIENT_ID=
MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET=
MARKET_JWT_SECRET_KEY=
MARKET_CORS_ALLOWED_ORIGIN=*
MARKET_CORS_ALLOWED_ORIGIN=*
MARKET_MONGO_LOG_LEVEL=DEBUG
3 changes: 2 additions & 1 deletion marketplace-build/release/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ services:
expose:
- 8080
volumes:
- /home/axonivy/marketplace/data/market-installations.json:/home/data/market-installation.json
- /home/axonivy/marketplace/data/market-installations.json:/data/market-installation.json
environment:
- MONGODB_HOST=${SERVICE_MONGODB_HOST}
- MONGODB_DATABASE=${SERVICE_MONGODB_DATABASE}
Expand All @@ -41,5 +41,6 @@ services:
- MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET=${MARKET_GITHUB_OAUTH_APP_CLIENT_SECRET}
- MARKET_JWT_SECRET_KEY=${MARKET_JWT_SECRET_KEY}
- MARKET_CORS_ALLOWED_ORIGIN=${MARKET_CORS_ALLOWED_ORIGIN}
- MARKET_MONGO_LOG_LEVEL=${MARKET_MONGO_LOG_LEVEL}
volumes:
mongodata:
1 change: 0 additions & 1 deletion marketplace-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package com.axonivy.market.assembler;

import com.axonivy.market.constants.RequestMappingConstants;
import com.axonivy.market.controller.ProductDetailsController;
import com.axonivy.market.entity.Product;
import com.axonivy.market.model.ProductDetailModel;
import com.axonivy.market.util.VersionUtils;
import lombok.extern.log4j.Log4j2;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Optional;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import com.axonivy.market.constants.RequestMappingConstants;
import com.axonivy.market.controller.ProductDetailsController;
import com.axonivy.market.entity.Product;
import com.axonivy.market.model.ProductDetailModel;
import com.axonivy.market.util.ImageUtils;
import com.axonivy.market.util.VersionUtils;

@Component
@Log4j2
public class ProductDetailModelAssembler extends RepresentationModelAssemblerSupport<Product, ProductDetailModel> {

private final ProductModelAssembler productModelAssembler;
Expand Down Expand Up @@ -80,6 +79,7 @@ private void createDetailResource(ProductDetailModel model, Product product) {
model.setContactUs(product.getContactUs());
model.setCost(product.getCost());
model.setInstallationCount(product.getInstallationCount());
model.setProductModuleContent(product.getProductModuleContent());
model.setProductModuleContent(ImageUtils.mappingImageForProductModuleContent(product.getProductModuleContent()));
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.axonivy.market.assembler;

import com.axonivy.market.controller.ImageController;
import com.axonivy.market.controller.ProductDetailsController;
import com.axonivy.market.entity.Product;
import com.axonivy.market.model.ProductModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
import org.springframework.stereotype.Component;

Expand All @@ -29,7 +31,9 @@ public ProductModel createResource(ProductModel model, Product product) {
model.setShortDescriptions(product.getShortDescriptions());
model.setType(product.getType());
model.setTags(product.getTags());
model.setLogoUrl(product.getLogoUrl());

Link logoLink = linkTo(methodOn(ImageController.class).findImageById(product.getLogoId())).withSelfRel();
model.setLogoUrl(logoLink.getHref());
return model;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.List;

import static com.axonivy.market.constants.CommonConstants.REQUESTED_BY;

@Configuration
Expand All @@ -20,16 +23,20 @@ public class MarketApiDocumentConfig {

@Bean
public GroupedOpenApi buildMarketCustomHeader() {
return GroupedOpenApi.builder().group(DEFAULT_DOC_GROUP).addOpenApiCustomizer(customMarketHeaders())
.pathsToMatch(PATH_PATTERN).build();
return GroupedOpenApi.builder().group(DEFAULT_DOC_GROUP)
.addOpenApiCustomizer(customMarketHeaders())
.pathsToMatch(PATH_PATTERN).build();
}

private OpenApiCustomizer customMarketHeaders() {
return openApi -> openApi.getPaths().values().forEach((PathItem pathItem) -> {
for (Operation operation : pathItem.readOperations()) {
Parameter headerParameter = new Parameter().in(HEADER_PARAM).schema(new StringSchema()).name(REQUESTED_BY)
.description(DEFAULT_PARAM).required(true);
operation.addParametersItem(headerParameter);
List<Operation> operations = Arrays.asList(pathItem.getPut(), pathItem.getPost(), pathItem.getPatch(), pathItem.getDelete());
for (Operation operation : operations) {
if (operation != null) {
Parameter headerParameter = new Parameter().in(HEADER_PARAM).schema(new StringSchema())
.name(REQUESTED_BY).description(DEFAULT_PARAM).required(true);
operation.addParametersItem(headerParameter);
}
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,21 @@
import io.swagger.v3.oas.models.PathItem.HttpMethod;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class MarketHeaderInterceptor implements HandlerInterceptor {

@Value("${request.header}")
private String requestHeader;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
return true;
}
if (!requestHeader.equals(request.getHeader(CommonConstants.REQUESTED_BY))) {
if (!HttpMethod.GET.name().equalsIgnoreCase(request.getMethod())
&& StringUtils.isBlank(request.getHeader(CommonConstants.REQUESTED_BY))) {
throw new MissingHeaderException();
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
public class WebConfig implements WebMvcConfigurer {

private static final String ALL_MAPPINGS = "/**";
private static final String[] EXCLUDE_PATHS = { "/", "/swagger-ui/**", "/api-docs/**",
"/api/product-details/productjsoncontent/**" , };
private static final String[] EXCLUDE_PATHS = { "/", "/swagger-ui/**", "/api-docs/**", "/api/product-details/**/json",
"/api/image/**" };
private static final String[] ALLOWED_HEADERS = { "Accept-Language", "Content-Type", "Authorization",
"X-Requested-By", "x-requested-with", "X-Forwarded-Host", "x-xsrf-token", "x-authorization" };
private static final String[] ALLOWED_METHODS = { "GET", "POST", "PUT", "DELETE", "OPTIONS" };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CommonConstants {
public static final String REQUESTED_BY = "X-Requested-By";
public static final String LOGO_FILE = "logo.png";
public static final String SLASH = "/";
public static final String DOT_SEPARATOR = ".";
public static final String PLUS = "+";
public static final String DASH_SEPARATOR = "-";
public static final String SPACE_SEPARATOR = " ";
public static final String BEARER = "Bearer";
public static final String DIGIT_REGEX = "([0-9]+.*)";
public static final String IMAGE_ID_PREFIX = "imageId-";
public static final String ID_WITH_NUMBER_PATTERN = "%s-%s";
public static final String ERROR = "error";
public static final String MESSAGE = "message";
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ public class EntityConstants {
public static final String PRODUCT_CUSTOM_SORT = "ProductCustomSort";
public static final String PRODUCT_JSON_CONTENT = "ProductJsonContent";
public static final String PRODUCT_MODULE_CONTENT = "ProductModuleContent";
public static final String IMAGE = "Image";
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ErrorMessageConstants {
public static final String INVALID_MISSING_HEADER_ERROR_MESSAGE = "Invalid or missing header";
public static final String CURRENT_CLIENT_ID_MISMATCH_MESSAGE = " Client ID mismatch (Request ID: %s, Server ID: %s)";
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,11 @@ private MongoDBConstants() {
}

public static final String ID ="_id";
public static final String PRODUCT_MODULE_CONTENT ="productModuleContent";
public static final String PRODUCT_MODULE_CONTENT_QUERY ="$productModuleContents";
public static final String INPUT ="input";
public static final String AS ="as";
public static final String CONDITION ="cond";
public static final String EQUAL ="$eq";
public static final String PRODUCT_MODULE_CONTENT_TAG ="$$productModuleContent.tag";
public static final String PRODUCT_COLLECTION ="Product";
public static final String INSTALLATION_COUNT = "InstallationCount";
public static final String SYNCHRONIZED_INSTALLATION_COUNT ="SynchronizedInstallationCount";
public static final String PRODUCT_ID = "productId";
public static final String DESIGNER_VERSION = "designerVersion";
public static final String TAG = "tag";

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public class ProductJsonConstants {
public static final String PRODUCT_JSON_FILE = "product.json";
public static final String LOGO_FILE = "logo.png";
public static final String DATA = "data";
public static final String REPOSITORIES = "repositories";
public static final String URL = "url";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ public class RequestMappingConstants {
public static final String PRODUCT_BY_ID = "/product/{id}";
public static final String PRODUCT_RATING_BY_ID = "/product/{id}/rating";
public static final String INSTALLATION_COUNT_BY_ID = "/installationcount/{id}";
public static final String PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION = "/productjsoncontent/{productId}/{version}";
public static final String PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION = "/{id}/{version}/json";
public static final String VERSIONS_IN_DESIGNER = "/{id}/designerversions";
public static final String DESIGNER_INSTALLATION_BY_PRODUCT_ID_AND_DESIGNER_VERSION = "/installation/{productId}/designer/{designerVersion}";
public static final String DESIGNER_INSTALLATION_BY_PRODUCT_ID = "/installation/{productId}/designer";
public static final String DESIGNER_INSTALLATION_BY_ID = "/installation/{id}/designer";
public static final String CUSTOM_SORT = "custom-sort";
public static final String IMAGE = API + "/image";
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RequestParamConstants {
public static final String ID = "id";
public static final String TAG = "tag";
public static final String TYPE = "type";
public static final String KEYWORD = "keyword";
public static final String LANGUAGE = "language";
Expand All @@ -17,5 +16,4 @@ public class RequestParamConstants {
public static final String SHOW_DEV_VERSION = "isShowDevVersion";
public static final String DESIGNER_VERSION = "designerVersion";
public static final String VERSION = "version";
public static final String PRODUCT_ID = "productId";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.axonivy.market.controller;

import com.axonivy.market.entity.Image;
import com.axonivy.market.service.ImageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static com.axonivy.market.constants.RequestMappingConstants.BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.IMAGE;
import static com.axonivy.market.constants.RequestParamConstants.ID;

@RestController
@RequestMapping(IMAGE)
@Tag(name = "Image Controllers", description = "API collection to get image's detail.")
public class ImageController {
private final ImageService imageService;

public ImageController(ImageService imageService) {
this.imageService = imageService;
}

@GetMapping(BY_ID)
@Operation(summary = "Get the image content by id", description = "Collect the byte[] of image with contentType in header is PNG")
@ApiResponse(responseCode = "200", description = "Image found and returned", content = @Content(mediaType = MediaType.IMAGE_PNG_VALUE, schema = @Schema(implementation = Image.class)))
@ApiResponse(responseCode = "404", description = "Image not found")
@ApiResponse(responseCode = "204", description = "No content (image empty)")
public ResponseEntity<byte[]> findImageById(
@PathVariable(ID) @Parameter(description = "The image id", example = "66e7efc8a24f36158df06fc7", in = ParameterIn.PATH) String id) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
byte[] imageData = imageService.readImage(id);
if (imageData == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

if (imageData.length == 0) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(imageData, headers, HttpStatus.OK);
}
}
Loading

0 comments on commit 30dd68d

Please sign in to comment.