Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

MARP-1687 Release Preview page #267

Merged
merged 12 commits into from
Dec 31, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.axonivy.market.constants;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class PreviewConstants {

public static final String PREVIEW_DIR = " marketplace-service/data/work";

public static final String IMAGE_DOWNLOAD_URL = "%s/api/image/preview/%s";

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class RequestMappingConstants {
public static final String GIT_HUB_LOGIN = "/github/login";
public static final String AUTH = "/auth";
public static final String BY_ID = "/{id}";
public static final String BY_FILE_NAME = "/preview/{imageName}";
public static final String BY_ID_AND_VERSION = "/{id}/{version}";
public static final String BEST_MATCH_BY_ID_AND_VERSION = "/{id}/{version}/bestmatch";
public static final String VERSIONS_BY_ID = "/{id}/versions";
Expand All @@ -33,4 +34,5 @@ public class RequestMappingConstants {
public static final String EXTERNAL_DOCUMENT = API + "/externaldocument";
public static final String PRODUCT_MARKETPLACE_DATA = API + "/product-marketplace-data";
public static final String SECURITY_MONITOR = API + "/security-monitor";
public static final String RELEASE_PREVIEW = API + "/release-preview";
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

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

@RestController
Expand Down Expand Up @@ -54,4 +55,25 @@ public ResponseEntity<byte[]> findImageById(
}
return new ResponseEntity<>(imageData, headers, HttpStatus.OK);
}

@GetMapping(BY_FILE_NAME)
@Operation(summary = "Get the preview image content by file name",
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[]> findPreviewImageByName(
@PathVariable("imageName") String imageName) {
quanpham-axonivy marked this conversation as resolved.
Show resolved Hide resolved
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
byte[] imageData = imageService.readPreviewImageByName(imageName);
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);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.axonivy.market.controller;

import com.axonivy.market.logging.Loggable;
import com.axonivy.market.model.DesignerInstallation;
import com.axonivy.market.service.ProductDesignerInstallationService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -30,6 +31,7 @@ public ProductDesignerInstallationController(ProductDesignerInstallationService
this.productDesignerInstallationService = productDesignerInstallationService;
}

@Loggable
@GetMapping(DESIGNER_INSTALLATION_BY_ID)
@Operation(summary = "Get designer installation count by product id.",
description = "get designer installation count by product id")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.axonivy.market.controller;

import com.axonivy.market.model.ReleasePreview;
import com.axonivy.market.service.ReleasePreviewService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import static com.axonivy.market.constants.RequestMappingConstants.RELEASE_PREVIEW;

@Log4j2
@RestController
@RequestMapping(RELEASE_PREVIEW)
@Tag(name = "Release Preview Controller", description = "API to extract zip file and return README data.")
@AllArgsConstructor
public class ReleasePreviewController {

private final ReleasePreviewService previewService;

@PostMapping
@Operation()
public ResponseEntity<Object> extractZipFile(@RequestParam(value = "file") MultipartFile file) {
String baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString();
ReleasePreview preview = previewService.extract(file, baseUrl);
if (preview == null) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return ResponseEntity.ok(preview);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.axonivy.market.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;

import java.util.Map;

import static com.axonivy.market.util.ProductContentUtils.DESCRIPTION;
import static com.axonivy.market.util.ProductContentUtils.DEMO;
import static com.axonivy.market.util.ProductContentUtils.SETUP;
import static com.axonivy.market.util.ProductContentUtils.replaceEmptyContentsWithEnContent;

quanpham-axonivy marked this conversation as resolved.
Show resolved Hide resolved
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ReleasePreview {

@Schema(description = "Product detail description content ",
example = "{ \"de\": \"E-Sign-Konnektor\", \"en\": \"E-sign connector\" }")
private Map<String, String> description;
@Schema(description = "Setup tab content", example = "{ \"de\": \"Setup\", \"en\": \"Setup\" ")
private Map<String, String> setup;
@Schema(description = "Demo tab content", example = "{ \"de\": \"Demo\", \"en\": \"Demo\" ")
private Map<String, String> demo;

public static ReleasePreview from(Map<String, Map<String, String>> moduleContents) {
return ReleasePreview.builder().description(replaceEmptyContentsWithEnContent(moduleContents.get(DESCRIPTION)))
.demo(replaceEmptyContentsWithEnContent(moduleContents.get(DEMO)))
.setup(replaceEmptyContentsWithEnContent(moduleContents.get(SETUP)))
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface ImageService {
Image mappingImageFromDownloadedFolder(String productId, Path imagePath);

byte[] readImage(String id);

byte[] readPreviewImageByName(String imageName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.axonivy.market.service;

import com.axonivy.market.model.ReleasePreview;
import org.springframework.web.multipart.MultipartFile;

public interface ReleasePreviewService {

ReleasePreview extract(MultipartFile file, String baseUrl);

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static com.axonivy.market.constants.PreviewConstants.PREVIEW_DIR;

@Service
@Log4j2
@AllArgsConstructor
Expand Down Expand Up @@ -109,4 +113,28 @@ public Image mappingImageFromDownloadedFolder(String productId, Path imagePath)
public byte[] readImage(String id) {
return imageRepository.findById(id).map(Image::getImageData).map(Binary::getData).orElse(null);
}

@Override
public byte[] readPreviewImageByName(String imageName) {
Path previewPath = Paths.get(PREVIEW_DIR);
if (!Files.exists(previewPath) || !Files.isDirectory(previewPath)) {
log.info("#readPreviewImageByName: Preview folder not found");
}
try {
Optional<Path> imagePath = Files.walk(previewPath)
.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().equalsIgnoreCase(imageName))
.findFirst();
if (imagePath.isEmpty()) {
log.info("#readPreviewImageByName: Image with name {} is missing", imageName);
return null;
}
InputStream contentStream = MavenUtils.extractedContentStream(imagePath.get());
return IOUtils.toByteArray(contentStream);
} catch (IOException e) {
log.error("#readPreviewImageByName: Error when read preview image {}: {}", imageName, e.getMessage());
return null;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.axonivy.market.service.impl;

import com.axonivy.market.constants.CommonConstants;
import com.axonivy.market.constants.ReadmeConstants;
import com.axonivy.market.model.ReadmeContentsModel;
import com.axonivy.market.model.ReleasePreview;
import com.axonivy.market.service.ReleasePreviewService;
import com.axonivy.market.util.FileUtils;
import com.axonivy.market.util.ProductContentUtils;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

import static com.axonivy.market.constants.PreviewConstants.IMAGE_DOWNLOAD_URL;
import static com.axonivy.market.constants.PreviewConstants.PREVIEW_DIR;

@Log4j2
@Service
@AllArgsConstructor
public class ReleasePreviewServiceImpl implements ReleasePreviewService {

quanpham-axonivy marked this conversation as resolved.
Show resolved Hide resolved
@Override
public ReleasePreview extract(MultipartFile file, String baseUrl) {
try {
FileUtils.unzip(file, PREVIEW_DIR);
quanpham-axonivy marked this conversation as resolved.
Show resolved Hide resolved
} catch (IOException e){
log.info("#extract Error extracting zip file, message: {}", e.getMessage());
return null;
}
return extractReadme(baseUrl, PREVIEW_DIR);
}

public ReleasePreview extractReadme(String baseUrl, String location) {
Map<String, Map<String, String>> moduleContents = new HashMap<>();
try (Stream<Path> readmePathStream = Files.walk(Paths.get(location))) {
List<Path> readmeFiles = readmePathStream.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().startsWith(ReadmeConstants.README_FILE_NAME))
.toList();
if (readmeFiles.isEmpty()) {
return null;
}
for (Path readmeFile : readmeFiles) {
processReadme(readmeFile, moduleContents, baseUrl, location);
}
return ReleasePreview.from(moduleContents);
} catch (IOException e) {
log.error("Cannot get README file's content from folder {}: {}",
PREVIEW_DIR, e.getMessage());
return null;
}
}

public String updateImagesWithDownloadUrl(String unzippedFolderPath,
String readmeContents, String baseUrl) {
Map<String, String> imageUrls = new HashMap<>();
try (Stream<Path> imagePathStream = Files.walk(Paths.get(unzippedFolderPath))) {
List<Path> allImagePaths = imagePathStream.filter(Files::isRegularFile).filter(
path -> path.getFileName().toString().toLowerCase().matches(CommonConstants.IMAGE_EXTENSION)).toList();

allImagePaths.stream()
.filter(Objects::nonNull)
.forEach(imagePath -> {
String imageFileName = imagePath.getFileName().toString();
String downloadURLFormat = String.format(IMAGE_DOWNLOAD_URL, baseUrl, imageFileName);
imageUrls.put(imageFileName, downloadURLFormat);
});
return ProductContentUtils.replaceImageDirWithImageCustomId(imageUrls, readmeContents);
} catch (Exception e) {
log.error("#updateImagesWithDownloadUrl: Error update image url: {}", e.getMessage());
return null;
}
}

public void processReadme(Path readmeFile, Map<String, Map<String, String>> moduleContents,
String baseUrl, String location) throws IOException {
String readmeContents = Files.readString(readmeFile);
if (ProductContentUtils.hasImageDirectives(readmeContents)) {
readmeContents = updateImagesWithDownloadUrl(location, readmeContents, baseUrl);
}
ReadmeContentsModel readmeContentsModel = ProductContentUtils.getExtractedPartsOfReadme(readmeContents);
ProductContentUtils.mappingDescriptionSetupAndDemo(
moduleContents,
readmeFile.getFileName().toString(),
readmeContentsModel
);
}

}
Loading
Loading