From 5203fc3605e14003ab69ac45c77d80eb8ad20bdc Mon Sep 17 00:00:00 2001 From: Felix Kaspar Date: Thu, 19 Oct 2023 19:46:23 +0200 Subject: [PATCH] sync & async api --- README.md | 52 +++++++- api/index.js | 1 - index.js | 4 +- package-lock.json | 276 +++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- routes/api/index.js | 12 ++ routes/api/workflow.js | 125 +++++++++++++++++++ traverseOperations.js | 206 ++++++++++++++++++++++++++++++ 8 files changed, 673 insertions(+), 8 deletions(-) delete mode 100644 api/index.js create mode 100644 routes/api/index.js create mode 100644 routes/api/workflow.js create mode 100644 traverseOperations.js diff --git a/README.md b/README.md index b8ec2d7b..5c2823ab 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,54 @@ This is the development repository for the new StirlingPDF backend. With the power of JS, WASM & GO this will provide almost all functionality SPDF can do currently directly on the client. For automation purposes this will still provide an API to automate your workflows. -## New/Planned Features +## Features -- Propper auth using passportjs -- Workflows & Node based editing of them. -- Client side PDF-Manipulation -- Stateful UI +### New + +[ ] Propper auth using passportjs +[x] Workflows +[ ] Node based editing of them. +[x] Client side PDF-Manipulation +[ ] Stateful UI + +### Functions + +Current functions of spdf and their progress in this repo. + +[x] Merge +[x] Split +[x] Rotate +[x] Multi-Page-Layout +[x] Adjust page size/scale +[ ] Organize +[ ] Change Metadata +[ ] Add Watermark + +[ ] Remove Pages +[ ] Remove Blank Pages +[ ] Detect/Split Scanned photos + +[ ] Repair +[ ] Compress +[ ] Flatten +[ ] Compare/Diff + +[ ] Sign +[ ] Sign with Certificate +[ ] Add Password +[ ] Remove Password +[ ] Change Permissions + +[ ] Image to PDF +[ ] Add image +[ ] Extract Images +[ ] PDF to Image +[ ] OCR + +[ ] Convert file to PDF +[ ] PDF to Text/RTF +[ ] PDF to HTML +[ ] PDF to XML ## Contribute diff --git a/api/index.js b/api/index.js deleted file mode 100644 index 191c2b04..00000000 --- a/api/index.js +++ /dev/null @@ -1 +0,0 @@ -// TODO: Make API endpoints available \ No newline at end of file diff --git a/index.js b/index.js index a45c4bfe..cf6c87ad 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,6 @@ +import api from './routes/api/index.js'; + import express from 'express'; const app = express(); const PORT = 8080; @@ -10,7 +12,7 @@ app.get('/', function (req, res, next) { // TODO: Use EJS? res.render('home.ejs'); }); -// TODO: Import and server /api +app.use("/api/", api); app.listen(PORT, function (err) { if (err) console.log(err); diff --git a/package-lock.json b/package-lock.json index 20c4e1e2..c408197e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,32 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "requires": { + "pako": "^1.0.6" + } + }, + "@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "requires": { + "pako": "^1.0.10" + } + }, + "@wasmer/wasmfs": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@wasmer/wasmfs/-/wasmfs-0.12.0.tgz", + "integrity": "sha512-m1ftchyQ1DfSenm5XbbdGIpb6KJHH5z0gODo3IZr6lATkj4WXfX/UeBTZ0aG9YVShBp+kHLdUHvOkqjy6p/GWw==", + "requires": { + "memfs": "3.0.4", + "pako": "^1.0.11", + "tar-stream": "^2.1.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -13,11 +39,31 @@ "negotiator": "0.6.3" } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -37,6 +83,28 @@ "unpipe": "1.0.0" } }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -51,6 +119,46 @@ "get-intrinsic": "^1.0.2" } }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -74,6 +182,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -102,6 +215,14 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -150,6 +271,11 @@ "vary": "~1.1.2" } }, + "fast-extend": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fast-extend/-/fast-extend-1.0.2.tgz", + "integrity": "sha512-XXA9RmlPatkFKUzqVZAFth18R4Wo+Xug/S+C7YlYA3xrXwfPlW3dqNwOb4hvQo7wZJ2cNDYhrYuPzVOfHy5/uQ==" + }, "finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -174,6 +300,16 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-monkey": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-0.3.3.tgz", + "integrity": "sha512-FNUvuTAJ3CqCQb5ELn+qCbGR/Zllhf2HtwsdAtBi59s1WeCjKMT81fHcSu7dwIskqGVK+MmOrb7VOBlq3/SItw==" + }, "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -225,6 +361,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -235,11 +376,25 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, + "memfs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.0.4.tgz", + "integrity": "sha512-OcZEzwX9E5AoY8SXjuAvw0DbIAYwUzV/I236I8Pqvrlv7sL/Y0E9aRCon05DhaV8pg1b32uxj76RgW0s5xjHBA==", + "requires": { + "fast-extend": "1.0.2", + "fs-monkey": "0.3.3" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -268,16 +423,48 @@ "mime-db": "1.52.0" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.0.tgz", @@ -291,6 +478,19 @@ "ee-first": "1.1.1" } }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -301,6 +501,22 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "requires": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -334,6 +550,16 @@ "unpipe": "1.0.0" } }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -402,11 +628,41 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -416,11 +672,21 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -430,6 +696,16 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" } } } diff --git a/package.json b/package.json index 8eb41455..7d29d67c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "author": "", "license": "ISC", "dependencies": { - "express": "^4.18.2" + "@wasmer/wasmfs": "^0.12.0", + "express": "^4.18.2", + "multer": "^1.4.5-lts.1", + "pdf-lib": "^1.17.1" }, "type": "module" } diff --git a/routes/api/index.js b/routes/api/index.js new file mode 100644 index 00000000..b75d742d --- /dev/null +++ b/routes/api/index.js @@ -0,0 +1,12 @@ +import express from 'express'; +import workflow from './workflow.js'; + +const router = express.Router(); + +router.get("/", function (req, res, next) { + res.status(501).json({"Error": "Unfinished Endpoint"}); +}); + +router.use("/workflow", workflow); + +export default router; \ No newline at end of file diff --git a/routes/api/workflow.js b/routes/api/workflow.js new file mode 100644 index 00000000..9f6c0190 --- /dev/null +++ b/routes/api/workflow.js @@ -0,0 +1,125 @@ +import express from 'express'; +import multer from 'multer'; +import crypto from 'crypto'; +import stream from "stream"; + +import { traverseOperations } from "../../traverseOperations.js"; + +const activeWorkflows = {}; + +const router = express.Router(); + +router.post("/", [ + multer().array("files"), + async (req, res, next) => { + const workflow = JSON.parse(req.body.workflow); + console.log("fileCount: ", req.files.length); + console.log("workflow: ", workflow); + + // TODO: Validate + + let workflowID = undefined; + + const inputs = await Promise.all(req.files.map(async file => { + console.log(file); + return { + originalFileName: file.originalname.replace(/\.[^/.]+$/, ""), + fileName: file.originalname.replace(/\.[^/.]+$/, ""), + buffer: new Uint8Array(await file.buffer) + } + })); + + // Allow option to do it synchronously and just make a long request + if(req.body.async === "false") { + console.log("Don't do async"); + + const pdfResults = await traverseOperations(workflow.operations, inputs); + downloadHandler(res, pdfResults); + } + else { + workflowID = generateWorkflowID(); + activeWorkflows[workflowID] = { + createdAt: Date.now(), + finished: false, + eventStream: null, + result: null, + // TODO: When auth is implemented: owner + } + + res.status(501).json({ + "warning": "Unfinished Endpoint", + "workflowID": workflowID, + "data-recieved": { + "fileCount": req.files.length, + "workflow": workflow + } + }); + + traverseOperations(workflow.operations, inputs).then((pdfResults) => { + activeWorkflows[workflowID].result = pdfResults; + activeWorkflows[workflowID].finished = true; + // TODO: Post to eventStream + }); + } + } +]); + +router.get("/progress/:workflowUuid", (req, res, nex) => { + // TODO: Validation + + // Return current progress + const workflow = activeWorkflows[req.params.workflowUuid]; + res.status(200).json({ createdAt: workflow.createdAt, finished: workflow.finished }); +}); + +router.get("/progress-stream/:workflowUuid", (req, res, nex) => { + // TODO: Send realtime updates + res.status(501).json({"warning": "Event-Stream has not been implemented yet."}); +}); + +router.get("/result/:workflowUuid", (req, res, nex) => { + // TODO: Validation + + /* + * If workflow isn't done return error + * Send file, TODO: if there are multiple outputs return as zip + * If download is done, delete results / allow deletion within the next 5-60 mins + */ + const workflow = activeWorkflows[req.params.workflowUuid]; + if(!workflow.finished) { + res.status(202).json({ message: "Workflow hasn't finished yet. Check progress or connect to progress-steam to get notified when its done." }); + return + } + + downloadHandler(res, workflow.result); + // Delete workflow / results when done. + delete activeWorkflows[req.params.workflowUuid]; +}); + +router.post("/abort/:workflowUuid", (req, res, nex) => { + // TODO: Abort workflow + res.status(501).json({"warning": "Abortion has not been implemented yet."}); +}); + +function generateWorkflowID() { + return crypto.randomUUID(); +} + +function downloadHandler(res, pdfResults) { + if(pdfResults.length > 1) { + res.status(501).json({"warning": "The workflow had multiple outputs, this is not implemented yet."}); + // TODO: Implement ZIP + } + else { + const readStream = new stream.PassThrough(); + readStream.end(pdfResults[0].buffer); + + // TODO: Implement other file types (mostly fro image & text extraction) + res.set("Content-disposition", 'attachment; filename=' + pdfResults[0].fileName + ".pdf"); + res.set("Content-Type", "application/pdf"); + + readStream.pipe(res); + } +} + +export default router; \ No newline at end of file diff --git a/traverseOperations.js b/traverseOperations.js new file mode 100644 index 00000000..d43ddf13 --- /dev/null +++ b/traverseOperations.js @@ -0,0 +1,206 @@ +import { extractPages } from "./functions/extractPages.js"; +import { impose } from "./functions/impose.js"; +import { mergePDFs } from "./functions/mergePDFs.js"; +import { rotatePages } from "./functions/rotatePDF.js"; +import { splitPDF } from "./functions/splitPDF.js"; +import { organizeWaitOperations } from "./public/organizeWaitOperations.js"; + +export async function traverseOperations(operations, input) { + const waitOperations = organizeWaitOperations(operations); + const results = []; + await nextOperation(operations, input); + return results; + + async function nextOperation(operations, input) { + if(Array.isArray(operations) && operations.length == 0) { // isEmpty + console.log("operation done: " + input.fileName); + results.push(input); + return; + } + + for (let i = 0; i < operations.length; i++) { + await computeOperation(operations[i], structuredClone(input)); // break references + } + } + + async function computeOperation(operation, input) { + switch (operation.type) { + case "done": + console.log("Done operation will get called if all waits are done. Skipping for now.") + break; + case "wait": + const waitOperation = waitOperations[operation.values.id]; + waitOperation.input.push(input); + waitOperation.waitCount--; + if(waitOperation.waitCount == 0) { + await nextOperation(waitOperation.doneOperation.operations, waitOperation.input); + } + break; + case "removeObjects": + console.warn("RemoveObjects not implemented yet.") + + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + // TODO: modfiy input + input[i].fileName += "_removedObjects"; + await nextOperation(operation.operations, input[i]); + } + } + else { + // TODO: modfiy input + input.fileName += "_removedObjects"; + await nextOperation(operation.operations, input); + } + break; + case "extract": + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + input[i].fileName += "_extractedPages"; + input[i].buffer = await extractPages(input[i].buffer, operation.values["pagesToExtractArray"]); + await nextOperation(operation.operations, input[i]); + } + } + else { + input.fileName += "_extractedPages"; + input.buffer = await extractPages(input.buffer, operation.values["pagesToExtractArray"]); + await nextOperation(operation.operations, input); + } + break; + case "split": + // TODO: When a split goes into a wait function it might break the done condition, as it will count multiplpe times. + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + const splits = await splitPDF(input[i].buffer, operation.values["pagesToSplitAfterArray"]); + + for (let j = 0; j < splits.length; j++) { + const split = {}; + split.originalFileName = input[i].originalFileName; + split.fileName = input[i].fileName + "_split"; + split.buffer = splits[j]; + await nextOperation(operation.operations, split); + } + } + } + else { + const splits = await splitPDF(input.buffer, operation.values["pagesToSplitAfterArray"]); + + for (let j = 0; j < splits.length; j++) { + const split = {}; + split.originalFileName = input.originalFileName; + split.fileName = input.fileName + "_split"; + split.buffer = splits[j]; + await nextOperation(operation.operations, split); + } + } + break; + case "fillField": + console.warn("FillField not implemented yet.") + + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + // TODO: modfiy input + input[i].fileName += "_filledField"; + await nextOperation(operation.operations, input[i]); + } + } + else { + // TODO: modfiy input + input.fileName += "_filledField"; + await nextOperation(operation.operations, input); + } + break; + case "extractImages": + console.warn("ExtractImages not implemented yet.") + + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + // TODO: modfiy input + input[i].fileName += "_extractedImages"; + await nextOperation(operation.operations, input[i]); + } + } + else { + // TODO: modfiy input + input.fileName += "_extractedImages"; + await nextOperation(operation.operations, input); + } + break; + case "merge": + if(Array.isArray(input) && input.length > 1) { + const inputs = input; + input = { + originalFileName: inputs.map(input => input.originalFileName).join("_and_"), + fileName: inputs.map(input => input.fileName).join("_and_") + "_merged", + buffer: await mergePDFs(inputs.map(input => input.buffer)) + } + } + else { + // Only one input, no need to merge + input.fileName += "_merged"; + } + await nextOperation(operation.operations, input); + break; + case "transform": { + console.warn("Transform not implemented yet.") + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + // TODO: modfiy input + input[i].fileName += "_transformed"; + await nextOperation(operation.operations, input[i]); + } + } + else { + // TODO: modfiy input + input.fileName += "_transformed"; + await nextOperation(operation.operations, input); + } + break; + } + case "extract": + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + input[i].fileName += "_extractedPages"; + input[i].buffer = await extractPages(input[i].buffer, operation.values["pagesToExtractArray"]); + await nextOperation(operation.operations, input[i]); + } + } + else { + input.fileName += "_extractedPages"; + input.buffer = await extractPages(input.buffer, operation.values["pagesToExtractArray"]); + await nextOperation(operation.operations, input); + } + break; + case "rotate": + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + input[i].fileName += "_turned"; + input[i].buffer = await rotatePages(input[i].buffer, operation.values["rotation"]); + await nextOperation(operation.operations, input[i]); + } + } + else { + input.fileName += "_turned"; + input.buffer = await rotatePages(input.buffer, operation.values["rotation"]); + await nextOperation(operation.operations, input); + } + break; + case "impose": + if(Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + input[i].fileName += "_imposed"; + input[i].buffer = await impose(input[i].buffer, operation.values["nup"], operation.values["format"]); + await nextOperation(operation.operations, input[i]); + } + } + else { + input.fileName += "_imposed"; + input.buffer = await impose(input.buffer, operation.values["nup"], operation.values["format"]); + await nextOperation(operation.operations, input); + } + break; + default: + console.log("operation type unknown: ", operation.type); + break; + } + } +} \ No newline at end of file