diff --git a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 3918fe2c..de6d255e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -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 mergePdfs( @RequestPart(required = true, value = "fileInput") diff --git a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java index 68ce8346..cdbf4079 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java @@ -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 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 = { diff --git a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index d3544007..e87a88e3 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -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 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) diff --git a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java index 0b2ac88f..acc40484 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java @@ -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 rotatePDF( @RequestPart(required = true, value = "fileInput") diff --git a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java index 837fd07b..fafe83e6 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -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 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 = { diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index 3f6305d2..4f69afb8 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -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 splitPdf( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be split") diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java index d5584044..6526dd90 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java @@ -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 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 convertToPdf( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input images to be converted to a PDF file") diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java index 14ee4558..8c3f72ad 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java @@ -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 processPdfWithOCR( @RequestPart(required = true, value = "fileInput") diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java index 1b9e2c5a..7f4a8e3b 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java @@ -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 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 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 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 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 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 { diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index d3b5dfe9..5d563f98 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -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 pdfToPdfA( @RequestPart(required = true, value = "fileInput") diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java b/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java index 0221d682..cc89451a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java @@ -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 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(); - } + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java index a7e0a70e..a20643f3 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java @@ -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 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 = { diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java b/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java index 3792b8dc..147195d3 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java @@ -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 extractImageScans( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input file containing image scans") diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImagesController.java b/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImagesController.java index 68a1d9ee..eb7d1c62 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImagesController.java @@ -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 extractImages( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file containing images") diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java b/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanControllerWIP.java similarity index 98% rename from src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java rename to src/main/java/stirling/software/SPDF/controller/api/other/FakeScanControllerWIP.java index a4876ad0..26541bb7 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanControllerWIP.java @@ -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 diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/MetadataController.java b/src/main/java/stirling/software/SPDF/controller/api/other/MetadataController.java index c5522ff8..ca9c522d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/MetadataController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/MetadataController.java @@ -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 metadata( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to update metadata") diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java b/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java index baa32889..61534aee 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java @@ -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 processPdfWithOCR( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be processed with OCR") diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/OverlayImageController.java b/src/main/java/stirling/software/SPDF/controller/api/other/OverlayImageController.java index db2ccf62..949466d9 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/OverlayImageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/OverlayImageController.java @@ -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 overlayImage( @RequestPart(required = true, value = "fileInput") diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java b/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java index d5edcf69..e076d34b 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java @@ -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 repairPdf( @RequestPart(required = true, value = "fileInput") diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/Controller.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/Controller.java index bfd4f742..d312e4dc 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/Controller.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/Controller.java @@ -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 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 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 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 processFiles(List 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 newOutputFiles = new ArrayList<>(); + boolean hasInputFileType = false; + + for (Resource file : outputFiles) { + if (file.getFilename().endsWith(inputFileExtension)) { + hasInputFileType = true; + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("fileInput", file); + + Iterator> parameters = parametersNode.fields(); + while (parameters.hasNext()) { + Map.Entry parameter = parameters.next(); + body.add(parameter.getKey(), parameter.getValue().asText()); + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + HttpEntity> entity = new HttpEntity<>(body, headers); + + RestTemplate restTemplate = new RestTemplate(); + String url = "http://localhost:8080/" + operation; + + ResponseEntity 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 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 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 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 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 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 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 newOutputFiles = new ArrayList<>(); - boolean hasInputFileType = false; - - for (Resource file : outputFiles) { - if (file.getFilename().endsWith(inputFileExtension)) { - hasInputFileType = true; - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("fileInput", file); - - Iterator> parameters = parametersNode.fields(); - while (parameters.hasNext()) { - Map.Entry parameter = parameters.next(); - body.add(parameter.getKey(), parameter.getValue().asText()); - } - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - - HttpEntity> entity = new HttpEntity<>(body, headers); - - RestTemplate restTemplate = new RestTemplate(); - String url = "http://localhost:8080/" + operation; - - ResponseEntity 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 outputFiles = handleFiles(files, jsonString); if (outputFiles.size() == 1) { // If there is only one file, return it directly diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index a053e486..6e95d175 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -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 signPDF( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be signed") diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index ecc27def..3555cb9c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -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 removePassword( @RequestPart(required = true, value = "fileInput") diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index d03f6868..ebc055b3 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -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 addWatermark( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to add a watermark") diff --git a/src/main/java/stirling/software/SPDF/model/PipelineConfig.java b/src/main/java/stirling/software/SPDF/model/PipelineConfig.java new file mode 100644 index 00000000..8fb7d32a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/PipelineConfig.java @@ -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 operations; + + private String outputDir; + + @JsonProperty("outputFileName") + private String outputPattern; + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getOperations() { + return operations; + } + + public void setOperations(List 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; + } + + +} diff --git a/src/main/java/stirling/software/SPDF/model/PipelineOperation.java b/src/main/java/stirling/software/SPDF/model/PipelineOperation.java new file mode 100644 index 00000000..8b079ba1 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/PipelineOperation.java @@ -0,0 +1,25 @@ +package stirling.software.SPDF.model; + +import java.util.Map; + +public class PipelineOperation { + private String operation; + private Map parameters; + + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public Map getParameters() { + return parameters; + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 67e6f807..7d7c8cec 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -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); diff --git a/src/main/resources/static/js/pipeline.js b/src/main/resources/static/js/pipeline.js new file mode 100644 index 00000000..2d8d2638 --- /dev/null +++ b/src/main/resources/static/js/pipeline.js @@ -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 = ` +
+
${selectedOperation}
+
+ + + + +
+
+ `; + + 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]); + }); + +}); \ No newline at end of file diff --git a/src/main/resources/templates/pipeline.html b/src/main/resources/templates/pipeline.html index 7cd246ed..f118ba6b 100644 --- a/src/main/resources/templates/pipeline.html +++ b/src/main/resources/templates/pipeline.html @@ -40,479 +40,90 @@
-
-
- -
-
-
-
- - -
- - -
+
+
+ +
+
+
+ + +
+
+ +
- -
- -
-

Pipeline Configuration

-
- - -
-
- + + +
+ +
+ + + +
+
+
+
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
\ No newline at end of file