init many new shit

This commit is contained in:
Anthony Stirling 2023-06-23 23:29:53 +01:00
parent e9daf05f16
commit 159cee0b39
28 changed files with 822 additions and 586 deletions

View file

@ -47,7 +47,7 @@ public class MergeController {
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
@Operation(
summary = "Merge multiple PDF files into one",
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF"
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO"
)
public ResponseEntity<byte[]> mergePdfs(
@RequestPart(required = true, value = "fileInput")

View file

@ -32,7 +32,7 @@ public class MultiPageLayoutController {
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF")
@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
@Parameter(description = "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", required = true, schema = @Schema(type = "integer", allowableValues = {

View file

@ -27,7 +27,7 @@ public class RearrangePagesPDFController {
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF")
@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> deletePages(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile,
@RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete)

View file

@ -26,7 +26,7 @@ public class RotationController {
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
@Operation(
summary = "Rotate a PDF file",
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF"
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO"
)
public ResponseEntity<byte[]> rotatePDF(
@RequestPart(required = true, value = "fileInput")

View file

@ -47,7 +47,7 @@ public class ScalePagesController {
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF")
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> scalePages(
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = {

View file

@ -38,7 +38,7 @@ public class SplitPDFController {
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
@Operation(summary = "Split a PDF file into separate documents",
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF")
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
public ResponseEntity<byte[]> splitPdf(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be split")

View file

@ -29,7 +29,7 @@ public class ConvertImgPDFController {
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
@Operation(summary = "Convert PDF to image(s)",
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image")
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional")
public ResponseEntity<Resource> convertToImage(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be converted")
@ -83,7 +83,7 @@ public class ConvertImgPDFController {
@PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
@Operation(summary = "Convert images to a PDF file",
description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF")
description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:MISO")
public ResponseEntity<byte[]> convertToPdf(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input images to be converted to a PDF file")

View file

@ -57,8 +57,8 @@ public class ConvertOfficeController {
@PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
@Operation(
summary = "Convert a file to a PDF using OCR",
description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF"
summary = "Convert a file to a PDF using LibreOffice",
description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO"
)
public ResponseEntity<byte[]> processPdfWithOCR(
@RequestPart(required = true, value = "fileInput")

View file

@ -18,7 +18,7 @@ import stirling.software.SPDF.utils.PDFToFile;
public class ConvertPDFToOffice {
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html")
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML")
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
public ResponseEntity<byte[]> processPdfToHTML(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile)
throws IOException, InterruptedException {
@ -27,7 +27,7 @@ public class ConvertPDFToOffice {
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation")
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT")
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO")
public ResponseEntity<byte[]> processPdfToPresentation(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
@RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = {
@ -38,7 +38,7 @@ public class ConvertPDFToOffice {
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text")
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT")
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO")
public ResponseEntity<byte[]> processPdfToRTForTXT(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
@RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = {
@ -49,7 +49,7 @@ public class ConvertPDFToOffice {
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word")
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD")
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO")
public ResponseEntity<byte[]> processPdfToWord(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
@RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = {
@ -60,7 +60,7 @@ public class ConvertPDFToOffice {
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml")
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML")
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO")
public ResponseEntity<byte[]> processPdfToXML(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile)
throws IOException, InterruptedException {

View file

@ -23,7 +23,7 @@ public class ConvertPDFToPDFA {
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
@Operation(
summary = "Convert a PDF to a PDF/A",
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF"
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO"
)
public ResponseEntity<byte[]> pdfToPdfA(
@RequestPart(required = true, value = "fileInput")

View file

@ -29,6 +29,7 @@ import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.pdf.ImageFinder;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -38,7 +39,7 @@ public class BlankPageController {
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
@Operation(
summary = "Remove blank pages from a PDF file",
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF"
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO"
)
public ResponseEntity<byte[]> removeBlankPages(
@RequestPart(required = true, value = "fileInput")
@ -71,7 +72,7 @@ public class BlankPageController {
pagesToKeepIndex.add(pageIndex);
System.out.println("page " + pageIndex + " has text");
} else {
boolean hasImages = hasImagesOnPage(page);
boolean hasImages = PdfUtils.hasImagesOnPage(page);
if (hasImages) {
System.out.println("page " + pageIndex + " has image");
@ -120,9 +121,5 @@ public class BlankPageController {
}
private static boolean hasImagesOnPage(PDPage page) throws IOException {
ImageFinder imageFinder = new ImageFinder(page);
imageFinder.processPage(page);
return imageFinder.hasImages();
}
}

View file

@ -41,7 +41,7 @@ public class CompressController {
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
@Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF")
@Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> optimizePdf(
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile,
@RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = {

View file

@ -41,7 +41,7 @@ public class ExtractImageScansController {
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
@Operation(summary = "Extract image scans from an input file",
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP")
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO")
public ResponseEntity<byte[]> extractImageScans(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input file containing image scans")

View file

@ -37,7 +37,7 @@ public class ExtractImagesController {
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
@Operation(summary = "Extract images from a PDF file",
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP")
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
public ResponseEntity<byte[]> extractImages(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file containing images")

View file

@ -54,9 +54,9 @@ import java.util.Random;
import io.swagger.v3.oas.annotations.Hidden;
@RestController
public class FakeScanController {
public class FakeScanControllerWIP {
private static final Logger logger = LoggerFactory.getLogger(FakeScanController.class);
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
//TODO
@Hidden

View file

@ -38,7 +38,7 @@ public class MetadataController {
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
@Operation(summary = "Update metadata of a PDF file",
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF")
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> metadata(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to update metadata")

View file

@ -47,7 +47,7 @@ public class OCRController {
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
@Operation(summary = "Process a PDF file with OCR",
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF")
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional")
public ResponseEntity<byte[]> processPdfWithOCR(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be processed with OCR")

View file

@ -25,7 +25,7 @@ public class OverlayImageController {
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
@Operation(
summary = "Overlay image onto a PDF file",
description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF"
description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO"
)
public ResponseEntity<byte[]> overlayImage(
@RequestPart(required = true, value = "fileInput")

View file

@ -27,7 +27,7 @@ public class RepairController {
@PostMapping(consumes = "multipart/form-data", value = "/repair")
@Operation(
summary = "Repair a PDF file",
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF"
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO"
)
public ResponseEntity<byte[]> repairPdf(
@RequestPart(required = true, value = "fileInput")

View file

@ -1,8 +1,10 @@
package stirling.software.SPDF.controller.api.pipeline;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
@ -12,15 +14,26 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.utils.WebResponseUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.io.*;
@ -32,97 +45,226 @@ import java.util.zip.ZipOutputStream;
@RestController
public class Controller {
@Autowired
private ObjectMapper objectMapper;
final String jsonFileName = "pipelineCofig.json";
final String watchedFoldersDir = "watchedFolders/";
@Scheduled(fixedRate = 5000)
public void scanFolders() {
try (Stream<Path> paths = Files.walk(Paths.get(watchedFoldersDir))) {
paths.filter(Files::isDirectory).forEach(t -> {
try {
handleDirectory(t);
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleDirectory(Path dir) throws Exception {
Path jsonFile = dir.resolve(jsonFileName);
if (Files.exists(jsonFile)) {
// Read JSON file
String jsonString;
try {
jsonString = new String(Files.readAllBytes(jsonFile));
} catch (IOException e) {
e.printStackTrace();
return;
}
// Decode JSON to PipelineConfig
PipelineConfig config;
try {
config = objectMapper.readValue(jsonString, PipelineConfig.class);
} catch (IOException e) {
e.printStackTrace();
return;
}
// For each operation in the pipeline
for (PipelineOperation operation : config.getOperations()) {
// Collect all files based on fileInput
File[] files;
String fileInput = (String) operation.getParameters().get("fileInput");
if ("automated".equals(fileInput)) {
// If fileInput is "automated", process all files in the directory
try (Stream<Path> paths = Files.list(dir)) {
files = paths.filter(path -> !path.equals(jsonFile))
.map(Path::toFile)
.toArray(File[]::new);
} catch (IOException e) {
e.printStackTrace();
return;
}
} else {
// If fileInput contains a path, process only this file
files = new File[]{new File(fileInput)};
}
// Call handleData for each operation
try {
List<Resource> resources = handleFiles(files, jsonString);
// Move resultant files and rename them as per config in JSON file
for (Resource resource : resources) {
String outputFileName = config.getOutputPattern().replace("{filename}", resource.getFile().getName());
outputFileName = outputFileName.replace("{pipelineName}", config.getName());
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
outputFileName = outputFileName.replace("{date}", LocalDate.now().format(dateFormatter));
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmmss");
outputFileName = outputFileName.replace("{time}", LocalTime.now().format(timeFormatter));
// ... Replace other placeholders
Files.move(resource.getFile().toPath(), Paths.get(config.getOutputDir(), outputFileName));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
List<Resource> processFiles(List<Resource> outputFiles, String jsonString) throws Exception{
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(jsonString);
JsonNode pipelineNode = jsonNode.get("pipeline");
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
PrintStream logPrintStream = new PrintStream(logStream);
boolean hasErrors = false;
for (JsonNode operationNode : pipelineNode) {
String operation = operationNode.get("operation").asText();
JsonNode parametersNode = operationNode.get("parameters");
String inputFileExtension = "";
if(operationNode.has("inputFileType")) {
inputFileExtension = operationNode.get("inputFileType").asText();
} else {
inputFileExtension=".pdf";
}
List<Resource> newOutputFiles = new ArrayList<>();
boolean hasInputFileType = false;
for (Resource file : outputFiles) {
if (file.getFilename().endsWith(inputFileExtension)) {
hasInputFileType = true;
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("fileInput", file);
Iterator<Map.Entry<String, JsonNode>> parameters = parametersNode.fields();
while (parameters.hasNext()) {
Map.Entry<String, JsonNode> parameter = parameters.next();
body.add(parameter.getKey(), parameter.getValue().asText());
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/" + operation;
ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
if (!response.getStatusCode().equals(HttpStatus.OK)) {
logPrintStream.println("Error: " + response.getBody());
hasErrors = true;
continue;
}
// Check if the response body is a zip file
if (isZip(response.getBody())) {
// Unzip the file and add all the files to the new output files
newOutputFiles.addAll(unzip(response.getBody()));
} else {
Resource outputResource = new ByteArrayResource(response.getBody()) {
@Override
public String getFilename() {
return file.getFilename(); // Preserving original filename
}
};
newOutputFiles.add(outputResource);
}
}
if (!hasInputFileType) {
logPrintStream.println("No files with extension " + inputFileExtension + " found for operation " + operation);
hasErrors = true;
}
outputFiles = newOutputFiles;
}
logPrintStream.close();
}
return outputFiles;
}
List<Resource> handleFiles(File[] files, String jsonString) throws Exception{
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(jsonString);
JsonNode pipelineNode = jsonNode.get("pipeline");
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
PrintStream logPrintStream = new PrintStream(logStream);
boolean hasErrors = false;
List<Resource> outputFiles = new ArrayList<>();
for (File file : files) {
Path path = Paths.get(file.getAbsolutePath());
Resource fileResource = new ByteArrayResource(Files.readAllBytes(path)) {
@Override
public String getFilename() {
return file.getName();
}
};
outputFiles.add(fileResource);
}
return processFiles(outputFiles, jsonString);
}
List<Resource> handleFiles(MultipartFile[] files, String jsonString) throws Exception{
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(jsonString);
JsonNode pipelineNode = jsonNode.get("pipeline");
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
PrintStream logPrintStream = new PrintStream(logStream);
boolean hasErrors = false;
List<Resource> outputFiles = new ArrayList<>();
for (MultipartFile file : files) {
Resource fileResource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename();
}
};
outputFiles.add(fileResource);
}
return processFiles(outputFiles, jsonString);
}
@PostMapping("/handleData")
public ResponseEntity<byte[]> handleData(@RequestPart("fileInput") MultipartFile[] files,
@RequestParam("json") String jsonString) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(jsonString);
JsonNode pipelineNode = jsonNode.get("pipeline");
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
PrintStream logPrintStream = new PrintStream(logStream);
boolean hasErrors = false;
List<Resource> outputFiles = new ArrayList<>();
for (MultipartFile file : files) {
Resource fileResource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename();
}
};
outputFiles.add(fileResource);
}
for (JsonNode operationNode : pipelineNode) {
String operation = operationNode.get("operation").asText();
JsonNode parametersNode = operationNode.get("parameters");
String inputFileExtension = "";
if(operationNode.has("inputFileType")) {
inputFileExtension = operationNode.get("inputFileType").asText();
} else {
inputFileExtension=".pdf";
}
List<Resource> newOutputFiles = new ArrayList<>();
boolean hasInputFileType = false;
for (Resource file : outputFiles) {
if (file.getFilename().endsWith(inputFileExtension)) {
hasInputFileType = true;
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("fileInput", file);
Iterator<Map.Entry<String, JsonNode>> parameters = parametersNode.fields();
while (parameters.hasNext()) {
Map.Entry<String, JsonNode> parameter = parameters.next();
body.add(parameter.getKey(), parameter.getValue().asText());
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/" + operation;
ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
if (!response.getStatusCode().equals(HttpStatus.OK)) {
logPrintStream.println("Error: " + response.getBody());
hasErrors = true;
continue;
}
// Check if the response body is a zip file
if (isZip(response.getBody())) {
// Unzip the file and add all the files to the new output files
newOutputFiles.addAll(unzip(response.getBody()));
} else {
Resource outputResource = new ByteArrayResource(response.getBody()) {
@Override
public String getFilename() {
return file.getFilename(); // Preserving original filename
}
};
newOutputFiles.add(outputResource);
}
}
if (!hasInputFileType) {
logPrintStream.println("No files with extension " + inputFileExtension + " found for operation " + operation);
hasErrors = true;
}
outputFiles = newOutputFiles;
}
logPrintStream.close();
}
List<Resource> outputFiles = handleFiles(files, jsonString);
if (outputFiles.size() == 1) {
// If there is only one file, return it directly

View file

@ -63,7 +63,7 @@ public class CertSignController {
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
@Operation(summary = "Sign PDF with a Digital Certificate",
description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF")
description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
public ResponseEntity<byte[]> signPDF(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be signed")

View file

@ -27,7 +27,7 @@ public class PasswordController {
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
@Operation(
summary = "Remove password from a PDF file",
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF"
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO"
)
public ResponseEntity<byte[]> removePassword(
@RequestPart(required = true, value = "fileInput")

View file

@ -33,7 +33,7 @@ public class WatermarkController {
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
@Operation(summary = "Add watermark to a PDF file",
description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF")
description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> addWatermark(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to add a watermark")

View file

@ -0,0 +1,52 @@
package stirling.software.SPDF.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
public class PipelineConfig {
private String name;
@JsonProperty("pipeline")
private List<PipelineOperation> operations;
private String outputDir;
@JsonProperty("outputFileName")
private String outputPattern;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<PipelineOperation> getOperations() {
return operations;
}
public void setOperations(List<PipelineOperation> operations) {
this.operations = operations;
}
public String getOutputDir() {
return outputDir;
}
public void setOutputDir(String outputDir) {
this.outputDir = outputDir;
}
public String getOutputPattern() {
return outputPattern;
}
public void setOutputPattern(String outputPattern) {
this.outputPattern = outputPattern;
}
}

View file

@ -0,0 +1,25 @@
package stirling.software.SPDF.model;
import java.util.Map;
public class PipelineOperation {
private String operation;
private Map<String, Object> parameters;
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public Map<String, Object> getParameters() {
return parameters;
}
public void setParameters(Map<String, Object> parameters) {
this.parameters = parameters;
}
}

View file

@ -27,14 +27,38 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
import com.itextpdf.kernel.pdf.canvas.parser.listener.SimpleTextExtractionStrategy;
import stirling.software.SPDF.pdf.ImageFinder;
public class PdfUtils {
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
public static boolean hasImagesOnPage(PDPage page) throws IOException {
ImageFinder imageFinder = new ImageFinder(page);
imageFinder.processPage(page);
return imageFinder.hasImages();
}
public static boolean hasTextOnPage(PdfPage page, String phrase) throws IOException {
String text = PdfTextExtractor.getTextFromPage(page, new SimpleTextExtractionStrategy());
return text.contains(phrase);
}
public static boolean hasText(PDDocument document, String phrase) throws IOException {
PDFTextStripper pdfStripper = new PDFTextStripper();
String text = pdfStripper.getText(document);
return text.contains(phrase);
}
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI, String filename) throws IOException, Exception {
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
PDFRenderer pdfRenderer = new PDFRenderer(document);

View file

@ -0,0 +1,385 @@
document.getElementById('validateButton').addEventListener('click', function(event) {
event.preventDefault();
validatePipeline();
});
function validatePipeline() {
let pipelineListItems = document.getElementById('pipelineList').children;
let isValid = true;
let containsAddPassword = false;
for (let i = 0; i < pipelineListItems.length - 1; i++) {
let currentOperation = pipelineListItems[i].querySelector('.operationName').textContent;
let nextOperation = pipelineListItems[i + 1].querySelector('.operationName').textContent;
if (currentOperation === '/add-password') {
containsAddPassword = true;
}
console.log(currentOperation);
console.log(apiDocs[currentOperation]);
let currentOperationDescription = apiDocs[currentOperation]?.post?.description || "";
let nextOperationDescription = apiDocs[nextOperation]?.post?.description || "";
console.log("currentOperationDescription", currentOperationDescription);
console.log("nextOperationDescription", nextOperationDescription);
let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || "";
let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || "";
console.log("Operation " + currentOperation + " Output: " + currentOperationOutput);
console.log("Operation " + nextOperation + " Input: " + nextOperationInput);
// Splitting in case of multiple possible output/input
let currentOperationOutputArr = currentOperationOutput.split('/');
let nextOperationInputArr = nextOperationInput.split('/');
if (currentOperationOutput !== 'ANY' && nextOperationInput !== 'ANY') {
let intersection = currentOperationOutputArr.filter(value => nextOperationInputArr.includes(value));
console.log(`Intersection: ${intersection}`);
if (intersection.length === 0) {
isValid = false;
console.log(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
alert(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
break;
}
}
}
if (containsAddPassword && pipelineListItems[pipelineListItems.length - 1].querySelector('.operationName').textContent !== '/add-password') {
alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.');
return false;
}
if (isValid) {
console.log('Pipeline is valid');
// Continue with the pipeline operation
} else {
console.error('Pipeline is not valid');
// Stop operation, maybe display an error to the user
}
return isValid;
}
document.getElementById('submitConfigBtn').addEventListener('click', function() {
if (validatePipeline() === false) {
return;
}
let selectedOperation = document.getElementById('operationsDropdown').value;
let parameters = operationSettings[selectedOperation] || {};
let pipelineConfig = {
"name": "uniquePipelineName",
"pipeline": [{
"operation": selectedOperation,
"parameters": parameters
}]
};
let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2);
let formData = new FormData();
let fileInput = document.getElementById('fileInput');
let files = fileInput.files;
for (let i = 0; i < files.length; i++) {
console.log("files[i]", files[i].name);
formData.append('fileInput', files[i], files[i].name);
}
console.log("pipelineConfigJson", pipelineConfigJson);
formData.append('json', pipelineConfigJson);
console.log("formData", formData);
fetch('/handleData', {
method: 'POST',
body: formData
})
.then(response => response.blob())
.then(blob => {
let url = window.URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = 'outputfile';
document.body.appendChild(a);
a.click();
a.remove();
})
.catch((error) => {
console.error('Error:', error);
});
});
let apiDocs = {};
let operationSettings = {};
fetch('v3/api-docs')
.then(response => response.json())
.then(data => {
apiDocs = data.paths;
let operationsDropdown = document.getElementById('operationsDropdown');
operationsDropdown.innerHTML = '';
Object.keys(apiDocs).forEach(operation => {
if (apiDocs[operation].hasOwnProperty('post')) {
let option = document.createElement('option');
option.textContent = operation;
operationsDropdown.appendChild(option);
}
});
});
document.getElementById('addOperationBtn').addEventListener('click', function() {
let selectedOperation = document.getElementById('operationsDropdown').value;
let pipelineList = document.getElementById('pipelineList');
let listItem = document.createElement('li');
listItem.className = "list-group-item";
listItem.innerHTML = `
<div class="d-flex justify-content-between align-items-center w-100">
<div class="operationName">${selectedOperation}</div>
<div class="arrows d-flex">
<button class="btn btn-secondary move-up btn-margin"><span>&uarr;</span></button>
<button class="btn btn-secondary move-down btn-margin"><span>&darr;</span></button>
<button class="btn btn-warning pipelineSettings btn-margin"><span></span></button>
<button class="btn btn-danger remove"><span>X</span></button>
</div>
</div>
`;
pipelineList.appendChild(listItem);
listItem.querySelector('.move-up').addEventListener('click', function(event) {
event.preventDefault();
if (listItem.previousElementSibling) {
pipelineList.insertBefore(listItem, listItem.previousElementSibling);
}
});
listItem.querySelector('.move-down').addEventListener('click', function(event) {
event.preventDefault();
if (listItem.nextElementSibling) {
pipelineList.insertBefore(listItem.nextElementSibling, listItem);
}
});
listItem.querySelector('.remove').addEventListener('click', function(event) {
event.preventDefault();
pipelineList.removeChild(listItem);
});
listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) {
event.preventDefault();
showpipelineSettingsModal(selectedOperation);
});
function showpipelineSettingsModal(operation) {
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
let operationData = apiDocs[operation].post.parameters || [];
pipelineSettingsContent.innerHTML = '';
operationData.forEach(parameter => {
let parameterDiv = document.createElement('div');
parameterDiv.className = "form-group";
let parameterLabel = document.createElement('label');
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
parameterLabel.title = parameter.description;
parameterDiv.appendChild(parameterLabel);
let parameterInput;
switch (parameter.schema.type) {
case 'string':
case 'number':
case 'integer':
parameterInput = document.createElement('input');
parameterInput.type = parameter.schema.type === 'string' ? 'text' : 'number';
parameterInput.className = "form-control";
break;
case 'boolean':
parameterInput = document.createElement('input');
parameterInput.type = 'checkbox';
break;
case 'array':
case 'object':
parameterInput = document.createElement('textarea');
parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}`;
parameterInput.className = "form-control";
break;
case 'enum':
parameterInput = document.createElement('select');
parameterInput.className = "form-control";
parameter.schema.enum.forEach(option => {
let optionElement = document.createElement('option');
optionElement.value = option;
optionElement.text = option;
parameterInput.appendChild(optionElement);
});
break;
default:
parameterInput = document.createElement('input');
parameterInput.type = 'text';
parameterInput.className = "form-control";
}
parameterInput.id = parameter.name;
if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
let savedValue = operationSettings[operation][parameter.name];
switch (parameter.schema.type) {
case 'number':
case 'integer':
parameterInput.value = savedValue.toString();
break;
case 'boolean':
parameterInput.checked = savedValue;
break;
case 'array':
case 'object':
parameterInput.value = JSON.stringify(savedValue);
break;
default:
parameterInput.value = savedValue;
}
}
parameterDiv.appendChild(parameterInput);
pipelineSettingsContent.appendChild(parameterDiv);
});
let saveButton = document.createElement('button');
saveButton.textContent = "Save Settings";
saveButton.className = "btn btn-primary";
saveButton.addEventListener('click', function(event) {
event.preventDefault();
let settings = {};
operationData.forEach(parameter => {
let value = document.getElementById(parameter.name).value;
switch (parameter.schema.type) {
case 'number':
case 'integer':
settings[parameter.name] = Number(value);
break;
case 'boolean':
settings[parameter.name] = document.getElementById(parameter.name).checked;
break;
case 'array':
case 'object':
try {
settings[parameter.name] = JSON.parse(value);
} catch (err) {
console.error(`Invalid JSON format for ${parameter.name}`);
}
break;
default:
settings[parameter.name] = value;
}
});
operationSettings[operation] = settings;
console.log(settings);
pipelineSettingsModal.style.display = "none";
});
pipelineSettingsContent.appendChild(saveButton);
pipelineSettingsModal.style.display = "block";
pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
pipelineSettingsModal.style.display = "none";
}
window.onclick = function(event) {
if (event.target == pipelineSettingsModal) {
pipelineSettingsModal.style.display = "none";
}
}
}
document.getElementById('savePipelineBtn').addEventListener('click', function() {
if (validatePipeline() === false) {
return;
}
let pipelineList = document.getElementById('pipelineList').children;
let pipelineConfig = {
"name": "uniquePipelineName",
"pipeline": []
};
for (let i = 0; i < pipelineList.length; i++) {
let operationName = pipelineList[i].querySelector('.operationName').textContent;
let parameters = operationSettings[operationName] || {};
pipelineConfig.pipeline.push({
"operation": operationName,
"parameters": parameters
});
}
let a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
type: 'application/json'
}));
a.download = 'pipelineConfig.json';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
document.getElementById('uploadPipelineBtn').addEventListener('click', function() {
document.getElementById('uploadPipelineInput').click();
});
document.getElementById('uploadPipelineInput').addEventListener('change', function(e) {
let reader = new FileReader();
reader.onload = function(event) {
let pipelineConfig = JSON.parse(event.target.result);
let pipelineList = document.getElementById('pipelineList');
while (pipelineList.firstChild) {
pipelineList.removeChild(pipelineList.firstChild);
}
pipelineConfig.pipeline.forEach(operationConfig => {
let operationsDropdown = document.getElementById('operationsDropdown');
operationsDropdown.value = operationConfig.operation;
operationSettings[operationConfig.operation] = operationConfig.parameters;
document.getElementById('addOperationBtn').click();
let lastOperation = pipelineList.lastChild;
lastOperation.querySelector('.pipelineSettings').click();
Object.keys(operationConfig.parameters).forEach(parameterName => {
let input = document.getElementById(parameterName);
if (input) {
switch (input.type) {
case 'checkbox':
input.checked = operationConfig.parameters[parameterName];
break;
case 'number':
input.value = operationConfig.parameters[parameterName].toString();
break;
case 'text':
case 'textarea':
default:
input.value = JSON.stringify(operationConfig.parameters[parameterName]);
}
}
});
document.querySelector('#pipelineSettingsModal .btn-primary').click();
});
};
reader.readAsText(e.target.files[0]);
});
});

View file

@ -40,479 +40,90 @@
</style>
<div id="page-container">
<div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<div class="container" id="dropContainer">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="mb-3">
<button id="savePipelineBtn" class="btn btn-success">Save Configuration</button>
<button id="validateButton" class="btn btn-success">Validate</button>
<div class="btn-group">
<button id="uploadPipelineBtn" class="btn btn-primary">Upload Configuration</button>
<input type="file" id="uploadPipelineInput" accept=".json" style="display: none;">
</div>
<div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<div class="container" id="dropContainer">
<div class="row justify-content-center">
<div class="col-md-6">
<!-- Homepage -->
<div class="mb-3">
<div class="form-group">
<label>Pipelines</label>
<select class="form-control" name="Pipelines">
<option value="example">example</option>
<option value="Pipelines2">example2</option>
</select>
</div>
<div id="pipelineContainer" class="card">
<!-- Pipeline Configuration Card Header -->
<div class="card-header">
<h2 class="card-title">Pipeline Configuration</h2>
</div>
<!-- Pipeline Configuration Body -->
<div class="card-body">
<div class="mb-3">
<select id="operationsDropdown" class="form-select">
<!-- Options will be dynamically populated here -->
</select>
<button id="customizeBtn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#pipelineModal">Customize</button>
<button id="addNewPipelineBtn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#pipelineModal">Add New Pipeline</button>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<button class="btn btn-primary" id="submitBtn">Submit</button>
</div>
<!-- Pipeline Modal -->
<div id="pipelineModal" class="modal fade" tabindex="-1" aria-labelledby="pipelineModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">Pipeline Configuration</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="mb-3">
<button id="addOperationBtn" class="btn btn-primary">Add operation to pipeline</button>
<div class="modal-body">
<!-- Buttons -->
<div class="mb-3">
<button id="savePipelineBtn" class="btn btn-success">Save Configuration</button>
<button id="validateButton" class="btn btn-success">Validate</button>
<div class="btn-group">
<button id="uploadPipelineBtn" class="btn btn-primary">Upload Configuration</button>
<input type="file" id="uploadPipelineInput" accept=".json" style="display: none;">
</div>
</div>
<!-- Operations dropdown and Add operation button -->
<div class="mb-3">
<select id="operationsDropdown" class="form-select">
<!-- Options will be dynamically populated here -->
</select>
<button id="addOperationBtn" class="btn btn-primary">Add operation to pipeline</button>
</div>
<!-- Pipeline operations list -->
<h3>Pipeline:</h3>
<ol id="pipelineList" class="list-group">
<!-- Pipeline operations will be dynamically populated here -->
</ol>
</div>
<h3>Pipeline:</h3>
<ol id="pipelineList" class="list-group">
<!-- Pipeline operations will be dynamically populated here -->
</ol>
</div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<button class="btn btn-primary" id="submitConfigBtn">Submit</button>
</div>
<!-- pipelineSettings modal -->
<div id="pipelineSettingsModal" class="modal">
<div class="modal-content">
<div class="modal-body">
<span class="close">&times;</span>
<h2>Operation Settings</h2>
<div id="pipelineSettingsContent">
<!-- pipelineSettings will be dynamically populated here -->
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="submitConfigBtn">Submit</button>
</div>
</div>
</div>
</div>
<!-- pipelineSettings modal -->
<div id="pipelineSettingsModal" class="modal">
<div class="modal-content">
<div class="modal-body">
<span class="close">&times;</span>
<h2>Operation Settings</h2>
<div id="pipelineSettingsContent">
<!-- pipelineSettings will be dynamically populated here -->
</div>
</div>
</div>
</div>
<script src="js/pipeline.js"></script>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
<script>
document.getElementById('validateButton').addEventListener('click', function(event) {
event.preventDefault();
validatePipeline();
});
function validatePipeline() {
let pipelineListItems = document.getElementById('pipelineList').children;
let isValid = true;
let containsAddPassword = false;
for (let i = 0; i < pipelineListItems.length - 1; i++) {
let currentOperation = pipelineListItems[i].querySelector('.operationName').textContent;
let nextOperation = pipelineListItems[i + 1].querySelector('.operationName').textContent;
if(currentOperation === '/add-password'){
containsAddPassword = true;
}
console.log(currentOperation);
console.log(apiDocs[currentOperation]);
let currentOperationDescription = apiDocs[currentOperation]?.post?.description || "";
let nextOperationDescription = apiDocs[nextOperation]?.post?.description || "";
console.log("currentOperationDescription",currentOperationDescription);
console.log("nextOperationDescription", nextOperationDescription);
let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || "";
let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || "";
console.log("Operation " + currentOperation + " Output: " + currentOperationOutput);
console.log("Operation " + nextOperation + " Input: " + nextOperationInput);
// Splitting in case of multiple possible output/input
let currentOperationOutputArr = currentOperationOutput.split('/');
let nextOperationInputArr = nextOperationInput.split('/');
if (currentOperationOutput !== 'ANY' && nextOperationInput !== 'ANY') {
let intersection = currentOperationOutputArr.filter(value => nextOperationInputArr.includes(value));
console.log(`Intersection: ${intersection}`);
if (intersection.length === 0) {
isValid = false;
console.log(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
alert(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
break;
}
}
}
if (containsAddPassword && pipelineListItems[pipelineListItems.length - 1].querySelector('.operationName').textContent !== '/add-password') {
alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.');
return false;
}
if (isValid) {
console.log('Pipeline is valid');
// Continue with the pipeline operation
} else {
console.error('Pipeline is not valid');
// Stop operation, maybe display an error to the user
}
return isValid;
}
document.getElementById('submitConfigBtn').addEventListener('click', function() {
if(validatePipeline() === false){
return;
}
let selectedOperation = document.getElementById('operationsDropdown').value;
let parameters = operationSettings[selectedOperation] || {};
let pipelineConfig = {
"name": "uniquePipelineName",
"pipeline": [{
"operation": selectedOperation,
"parameters": parameters
}]
};
let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2);
let formData = new FormData();
let fileInput = document.getElementById('fileInput');
let files = fileInput.files;
for(let i = 0; i < files.length; i++) {
console.log("files[i]",files[i].name);
formData.append('fileInput', files[i], files[i].name);
}
console.log("pipelineConfigJson",pipelineConfigJson);
formData.append('json', pipelineConfigJson);
console.log("formData",formData);
fetch('/handleData', {
method: 'POST',
body: formData
})
.then(response => response.blob())
.then(blob => {
let url = window.URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = 'outputfile';
document.body.appendChild(a);
a.click();
a.remove();
})
.catch((error) => {
console.error('Error:', error);
});
});
let apiDocs = {};
let operationSettings = {};
fetch('v3/api-docs')
.then(response => response.json())
.then(data => {
apiDocs = data.paths;
let operationsDropdown = document.getElementById('operationsDropdown');
operationsDropdown.innerHTML = '';
Object.keys(apiDocs).forEach(operation => {
if(apiDocs[operation].hasOwnProperty('post')) {
let option = document.createElement('option');
option.textContent = operation;
operationsDropdown.appendChild(option);
}
});
});
document.getElementById('addOperationBtn').addEventListener('click', function() {
let selectedOperation = document.getElementById('operationsDropdown').value;
let pipelineList = document.getElementById('pipelineList');
let listItem = document.createElement('li');
listItem.className = "list-group-item";
listItem.innerHTML = `
<div class="d-flex justify-content-between align-items-center w-100">
<div class="operationName">${selectedOperation}</div>
<div class="arrows d-flex">
<button class="btn btn-secondary move-up btn-margin"><span>&uarr;</span></button>
<button class="btn btn-secondary move-down btn-margin"><span>&darr;</span></button>
<button class="btn btn-warning pipelineSettings btn-margin"><span>⚙️</span></button>
<button class="btn btn-danger remove"><span>X</span></button>
</div>
</div>
`;
pipelineList.appendChild(listItem);
listItem.querySelector('.move-up').addEventListener('click', function(event) {
event.preventDefault();
if (listItem.previousElementSibling) {
pipelineList.insertBefore(listItem, listItem.previousElementSibling);
}
});
listItem.querySelector('.move-down').addEventListener('click', function(event) {
event.preventDefault();
if (listItem.nextElementSibling) {
pipelineList.insertBefore(listItem.nextElementSibling, listItem);
}
});
listItem.querySelector('.remove').addEventListener('click', function(event) {
event.preventDefault();
pipelineList.removeChild(listItem);
});
listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) {
event.preventDefault();
showpipelineSettingsModal(selectedOperation);
});
function showpipelineSettingsModal(operation) {
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
let operationData = apiDocs[operation].post.parameters || [];
pipelineSettingsContent.innerHTML = '';
operationData.forEach(parameter => {
let parameterDiv = document.createElement('div');
parameterDiv.className = "form-group";
let parameterLabel = document.createElement('label');
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
parameterLabel.title = parameter.description;
parameterDiv.appendChild(parameterLabel);
let parameterInput;
switch(parameter.schema.type) {
case 'string':
case 'number':
case 'integer':
parameterInput = document.createElement('input');
parameterInput.type = parameter.schema.type === 'string' ? 'text' : 'number';
parameterInput.className = "form-control";
break;
case 'boolean':
parameterInput = document.createElement('input');
parameterInput.type = 'checkbox';
break;
case 'array':
case 'object':
parameterInput = document.createElement('textarea');
parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}`;
parameterInput.className = "form-control";
break;
case 'enum':
parameterInput = document.createElement('select');
parameterInput.className = "form-control";
parameter.schema.enum.forEach(option => {
let optionElement = document.createElement('option');
optionElement.value = option;
optionElement.text = option;
parameterInput.appendChild(optionElement);
});
break;
default:
parameterInput = document.createElement('input');
parameterInput.type = 'text';
parameterInput.className = "form-control";
}
parameterInput.id = parameter.name;
if(operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
let savedValue = operationSettings[operation][parameter.name];
switch(parameter.schema.type) {
case 'number':
case 'integer':
parameterInput.value = savedValue.toString();
break;
case 'boolean':
parameterInput.checked = savedValue;
break;
case 'array':
case 'object':
parameterInput.value = JSON.stringify(savedValue);
break;
default:
parameterInput.value = savedValue;
}
}
parameterDiv.appendChild(parameterInput);
pipelineSettingsContent.appendChild(parameterDiv);
});
let saveButton = document.createElement('button');
saveButton.textContent = "Save Settings";
saveButton.className = "btn btn-primary";
saveButton.addEventListener('click', function(event) {
event.preventDefault();
let settings = {};
operationData.forEach(parameter => {
let value = document.getElementById(parameter.name).value;
switch(parameter.schema.type) {
case 'number':
case 'integer':
settings[parameter.name] = Number(value);
break;
case 'boolean':
settings[parameter.name] = document.getElementById(parameter.name).checked;
break;
case 'array':
case 'object':
try {
settings[parameter.name] = JSON.parse(value);
} catch(err) {
console.error(`Invalid JSON format for ${parameter.name}`);
}
break;
default:
settings[parameter.name] = value;
}
});
operationSettings[operation] = settings;
console.log(settings);
pipelineSettingsModal.style.display = "none";
});
pipelineSettingsContent.appendChild(saveButton);
pipelineSettingsModal.style.display = "block";
pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
pipelineSettingsModal.style.display = "none";
}
window.onclick = function(event) {
if (event.target == pipelineSettingsModal) {
pipelineSettingsModal.style.display = "none";
}
}
}
document.getElementById('savePipelineBtn').addEventListener('click', function() {
if(validatePipeline() === false){
return;
}
let pipelineList = document.getElementById('pipelineList').children;
let pipelineConfig = {
"name": "uniquePipelineName",
"pipeline": []
};
for(let i=0; i<pipelineList.length; i++) {
let operationName = pipelineList[i].querySelector('.operationName').textContent;
let parameters = operationSettings[operationName] || {};
pipelineConfig.pipeline.push({
"operation": operationName,
"parameters": parameters
});
}
let a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
type: 'application/json'
}));
a.download = 'pipelineConfig.json';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
document.getElementById('uploadPipelineBtn').addEventListener('click', function() {
document.getElementById('uploadPipelineInput').click();
});
document.getElementById('uploadPipelineInput').addEventListener('change', function(e) {
let reader = new FileReader();
reader.onload = function(event) {
let pipelineConfig = JSON.parse(event.target.result);
let pipelineList = document.getElementById('pipelineList');
while(pipelineList.firstChild) {
pipelineList.removeChild(pipelineList.firstChild);
}
pipelineConfig.pipeline.forEach(operationConfig => {
let operationsDropdown = document.getElementById('operationsDropdown');
operationsDropdown.value = operationConfig.operation;
operationSettings[operationConfig.operation] = operationConfig.parameters;
document.getElementById('addOperationBtn').click();
let lastOperation = pipelineList.lastChild;
lastOperation.querySelector('.pipelineSettings').click();
Object.keys(operationConfig.parameters).forEach(parameterName => {
let input = document.getElementById(parameterName);
if(input) {
switch(input.type) {
case 'checkbox':
input.checked = operationConfig.parameters[parameterName];
break;
case 'number':
input.value = operationConfig.parameters[parameterName].toString();
break;
case 'text':
case 'textarea':
default:
input.value = JSON.stringify(operationConfig.parameters[parameterName]);
}
}
});
document.querySelector('#pipelineSettingsModal .btn-primary').click();
});
};
reader.readAsText(e.target.files[0]);
});
});
</script>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>