diff --git a/Utilities/image_answering_utility/README.md b/Utilities/image_answering_utility/README.md new file mode 100644 index 00000000..b7ccb4af --- /dev/null +++ b/Utilities/image_answering_utility/README.md @@ -0,0 +1,52 @@ +PDF Processor +============== + +Overview: +--------- +PDF Processor is a service for processing PDF files. It supports both individual PDF processing and batch processing of directories containing multiple PDFs. The service can also extract PDFs from archives (e.g., ZIP files) and integrates with MongoDB and AWS services. + +Features: +--------- +- Process single PDF files or entire directories. +- Automatically extract PDFs from ZIP or other archive formats (.tar, .gz, .tgz). +- Asynchronous processing with a configurable maximum number of concurrent tasks (set via the MAX_CONCURRENT_PROCESSING environment variable). +- MongoDB integration for tracking processing status. +- Logging for monitoring and error handling. +- Convert PDF pages into markdown representations and corresponding image snapshots. +- Upload processed results and generated images to AWS S3 for URL generation. + +Tech Stack: +----------- +- Node.js, Express +- MongoDB & Mongoose +- AWS SDK (S3, etc.) +- Various utilities: axios, bull, pdf2pic, extract-zip, and more + +Setup: +------ +1. Clone the repository. +2. (Optional) Initialize or install MongoDB using: + - `npm run init-mongodb` + - `npm run install-mongodb` +3. Make the setup scripts executable: run `chmod +x setup/*.sh` +4. Start the API service by running `./setup/start_api_service.sh` + +Example cURL Commands: +------------------------ +To test the API endpoint for processing a PDF from a URL without base64 encoding, run: + +curl -X POST http://localhost:3000/process-directory-from-url \ + -H "Content-Type: application/json" \ + -d '{"downloadUrl": "https://censusindia.gov.in/nada/index.php/catalog/28594/download/31776/50187_1961_DEM.pdf", "include_base64": false}' + +Directory Structure: +-------------------- +- src/ + - api/ : API layer (server.js, etc.) + - services/ : PDF processing and image service implementations + - scripts/ : Utility and setup scripts + - config/ : Configuration files (including environment variables) +- utils/ : Helper utilities +- storage/ : Temporary directories and file storage + + diff --git a/Utilities/image_answering_utility/config/.env b/Utilities/image_answering_utility/config/.env new file mode 100644 index 00000000..f2afb6fc --- /dev/null +++ b/Utilities/image_answering_utility/config/.env @@ -0,0 +1,46 @@ +# Server Configuration +HOST=0.0.0.0 +PORT=3000 +MAX_CONCURRENT_PROCESSING=3 + +# AWS Credentials +AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY+hynhGJdL79 +AWS_REGION=AWS_REGION + +# S3 Configuration +S3_BUCKET_NAME=S3_BUCKET_NAME +S3_FOLDER_PATH=S3_FOLDER_PATH +S3_URL_EXPIRY=S3_URL_EXPIRY + +# Service URLs +MARKDOWN_SERVICE_URL=http://0.0.0.0:8000/process-pdf-markdown/ +CALLBACK_SERVICE_URL=http://localhost:8081/callback + +# Processing Configuration +MAX_CONCURRENT_FILES=3 +MAX_RETRIES=5 +RETRY_DELAY=5000 +REQUEST_TIMEOUT=300000 +CALLBACK_RETRIES=3 +CALLBACK_RETRY_DELAY=5000 + +# Directory & Logging Configuration +LOGS_DIR=logs +LOG_LEVEL=info + +# Redis Configuration +REDIS_HOST=localhost +REDIS_PORT=6379 +QUEUE_CONCURRENCY=3 + +# PDF & Image Processing Configuration +PDF_IMAGE_DENSITY=150 +PDF_IMAGE_FORMAT=png +PDF_IMAGE_WIDTH=1200 +PDF_IMAGE_HEIGHT=1600 +PDF_IMAGE_QUALITY=80 +PDF_PRESERVE_ASPECT_RATIO=true + +# MongoDB Configuration +MONGODB_URI=mongodb://localhost:27017/pdf-processor diff --git a/Utilities/image_answering_utility/package-lock.json b/Utilities/image_answering_utility/package-lock.json new file mode 100644 index 00000000..d6c4991b --- /dev/null +++ b/Utilities/image_answering_utility/package-lock.json @@ -0,0 +1,3198 @@ +{ + "name": "pdf-processor", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "requires": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "requires": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@aws-sdk/client-s3": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.750.0.tgz", + "integrity": "sha512-S9G9noCeBxchoMVkHYrRi1A1xW/VOTP2W7X34lP+Y7Wpl32yMA7IJo0fAGAuTc0q1Nu6/pXDm+oDG7rhTCA1tg==", + "requires": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/credential-provider-node": "3.750.0", + "@aws-sdk/middleware-bucket-endpoint": "3.734.0", + "@aws-sdk/middleware-expect-continue": "3.734.0", + "@aws-sdk/middleware-flexible-checksums": "3.750.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-location-constraint": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-sdk-s3": "3.750.0", + "@aws-sdk/middleware-ssec": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/signature-v4-multi-region": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.750.0", + "@aws-sdk/xml-builder": "3.734.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.4", + "@smithy/eventstream-serde-browser": "^4.0.1", + "@smithy/eventstream-serde-config-resolver": "^4.0.1", + "@smithy/eventstream-serde-node": "^4.0.1", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-blob-browser": "^4.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/hash-stream-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/md5-js": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/middleware-retry": "^4.0.6", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.6", + "@smithy/util-defaults-mode-node": "^4.0.6", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-stream": "^4.1.1", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.2", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/client-sso": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.750.0.tgz", + "integrity": "sha512-y0Rx6pTQXw0E61CaptpZF65qNggjqOgymq/RYZU5vWba5DGQ+iqGt8Yq8s+jfBoBBNXshxq8l8Dl5Uq/JTY1wg==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.750.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.4", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/middleware-retry": "^4.0.6", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.6", + "@smithy/util-defaults-mode-node": "^4.0.6", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/core": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.750.0.tgz", + "integrity": "sha512-bZ5K7N5L4+Pa2epbVpUQqd1XLG2uU8BGs/Sd+2nbgTf+lNQJyIxAg/Qsrjz9MzmY8zzQIeRQEkNmR6yVAfCmmQ==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/core": "^3.1.4", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/signature-v4": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.750.0.tgz", + "integrity": "sha512-In6bsG0p/P31HcH4DBRKBbcDS/3SHvEPjfXV8ODPWZO/l3/p7IRoYBdQ07C9R+VMZU2D0+/Sc/DWK/TUNDk1+Q==", + "requires": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-http": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.750.0.tgz", + "integrity": "sha512-wFB9qqfa20AB0dElsQz5ZlZT5o+a+XzpEpmg0erylmGYqEOvh8NQWfDUVpRmQuGq9VbvW/8cIbxPoNqEbPtuWQ==", + "requires": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/property-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.1.1", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.750.0.tgz", + "integrity": "sha512-2YIZmyEr5RUd3uxXpxOLD9G67Bibm4I/65M6vKFP17jVMUT+R1nL7mKqmhEVO2p+BoeV+bwMyJ/jpTYG368PCg==", + "requires": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/credential-provider-env": "3.750.0", + "@aws-sdk/credential-provider-http": "3.750.0", + "@aws-sdk/credential-provider-process": "3.750.0", + "@aws-sdk/credential-provider-sso": "3.750.0", + "@aws-sdk/credential-provider-web-identity": "3.750.0", + "@aws-sdk/nested-clients": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.750.0.tgz", + "integrity": "sha512-THWHHAceLwsOiowPEmKyhWVDlEUxH07GHSw5AQFDvNQtGKOQl0HSIFO1mKObT2Q2Vqzji9Bq8H58SO5BFtNPRw==", + "requires": { + "@aws-sdk/credential-provider-env": "3.750.0", + "@aws-sdk/credential-provider-http": "3.750.0", + "@aws-sdk/credential-provider-ini": "3.750.0", + "@aws-sdk/credential-provider-process": "3.750.0", + "@aws-sdk/credential-provider-sso": "3.750.0", + "@aws-sdk/credential-provider-web-identity": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.750.0.tgz", + "integrity": "sha512-Q78SCH1n0m7tpu36sJwfrUSxI8l611OyysjQeMiIOliVfZICEoHcLHLcLkiR+tnIpZ3rk7d2EQ6R1jwlXnalMQ==", + "requires": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.750.0.tgz", + "integrity": "sha512-FGYrDjXN/FOQVi/t8fHSv8zCk+NEvtFnuc4cZUj5OIbM4vrfFc5VaPyn41Uza3iv6Qq9rZg0QOwWnqK8lNrqUw==", + "requires": { + "@aws-sdk/client-sso": "3.750.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/token-providers": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.750.0.tgz", + "integrity": "sha512-Nz8zs3YJ+GOTSrq+LyzbbC1Ffpt7pK38gcOyNZv76pP5MswKTUKNYBJehqwa+i7FcFQHsCk3TdhR8MT1ZR23uA==", + "requires": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/nested-clients": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-bucket-endpoint": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.734.0.tgz", + "integrity": "sha512-etC7G18aF7KdZguW27GE/wpbrNmYLVT755EsFc8kXpZj8D6AFKxc7OuveinJmiy0bYXAMspJUWsF6CrGpOw6CQ==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-arn-parser": "3.723.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-expect-continue": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.734.0.tgz", + "integrity": "sha512-P38/v1l6HjuB2aFUewt7ueAW5IvKkFcv5dalPtbMGRhLeyivBOHwbCyuRKgVs7z7ClTpu9EaViEGki2jEQqEsQ==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-flexible-checksums": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.750.0.tgz", + "integrity": "sha512-ach0d2buDnX2TUausUbiXXFWFo3IegLnCrA+Rw8I9AYVpLN9lTaRwAYJwYC6zEuW9Golff8MwkYsp/OaC5tKMw==", + "requires": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-stream": "^4.1.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "requires": { + "tslib": "^2.6.2" + } + } + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.734.0.tgz", + "integrity": "sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-location-constraint": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.734.0.tgz", + "integrity": "sha512-EJEIXwCQhto/cBfHdm3ZOeLxd2NlJD+X2F+ZTOxzokuhBtY0IONfC/91hOo5tWQweerojwshSMHRCKzRv1tlwg==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.734.0.tgz", + "integrity": "sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.734.0.tgz", + "integrity": "sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-sdk-s3": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.750.0.tgz", + "integrity": "sha512-3H6Z46cmAQCHQ0z8mm7/cftY5ifiLfCjbObrbyyp2fhQs9zk6gCKzIX8Zjhw0RMd93FZi3ebRuKJWmMglf4Itw==", + "requires": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-arn-parser": "3.723.0", + "@smithy/core": "^3.1.4", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/signature-v4": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-stream": "^4.1.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-ssec": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.734.0.tgz", + "integrity": "sha512-d4yd1RrPW/sspEXizq2NSOUivnheac6LPeLSLnaeTbBG9g1KqIqvCzP1TfXEqv2CrWfHEsWtJpX7oyjySSPvDQ==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.750.0.tgz", + "integrity": "sha512-YYcslDsP5+2NZoN3UwuhZGkhAHPSli7HlJHBafBrvjGV/I9f8FuOO1d1ebxGdEP4HyRXUGyh+7Ur4q+Psk0ryw==", + "requires": { + "@aws-sdk/core": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@smithy/core": "^3.1.4", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/nested-clients": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.750.0.tgz", + "integrity": "sha512-OH68BRF0rt9nDloq4zsfeHI0G21lj11a66qosaljtEP66PWm7tQ06feKbFkXHT5E1K3QhJW3nVyK8v2fEBY5fg==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.750.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.750.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.4", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/middleware-retry": "^4.0.6", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.6", + "@smithy/util-defaults-mode-node": "^4.0.6", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/region-config-resolver": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.734.0.tgz", + "integrity": "sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/s3-request-presigner": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.750.0.tgz", + "integrity": "sha512-G4GNngNQlh9EyJZj2WKOOikX0Fev1WSxTV/XJugaHlpnVriebvi3GzolrgxUpRrcGpFGWjmAxLi/gYxTUla1ow==", + "requires": { + "@aws-sdk/signature-v4-multi-region": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-format-url": "3.734.0", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/signature-v4-multi-region": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.750.0.tgz", + "integrity": "sha512-RA9hv1Irro/CrdPcOEXKwJ0DJYJwYCsauGEdRXihrRfy8MNSR9E+mD5/Fr5Rxjaq5AHM05DYnN3mg/DU6VwzSw==", + "requires": { + "@aws-sdk/middleware-sdk-s3": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/signature-v4": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/token-providers": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.750.0.tgz", + "integrity": "sha512-X/KzqZw41iWolwNdc8e3RMcNSMR364viHv78u6AefXOO5eRM40c4/LuST1jDzq35/LpnqRhL7/MuixOetw+sFw==", + "requires": { + "@aws-sdk/nested-clients": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/types": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.734.0.tgz", + "integrity": "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-arn-parser": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.723.0.tgz", + "integrity": "sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.743.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.743.0.tgz", + "integrity": "sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "@smithy/util-endpoints": "^3.0.1", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-format-url": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.734.0.tgz", + "integrity": "sha512-TxZMVm8V4aR/QkW9/NhujvYpPZjUYqzLwSge5imKZbWFR806NP7RMwc5ilVuHF/bMOln/cVHkl42kATElWBvNw==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz", + "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.734.0.tgz", + "integrity": "sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==", + "requires": { + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.750.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.750.0.tgz", + "integrity": "sha512-84HJj9G9zbrHX2opLk9eHfDceB+UIHVrmflMzWHpsmo9fDuro/flIBqaVDlE021Osj6qIM0SJJcnL6s23j7JEw==", + "requires": { + "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/types": "3.734.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/xml-builder": { + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.734.0.tgz", + "integrity": "sha512-Zrjxi5qwGEcUsJ0ru7fRtW74WcTS0rbLcehoFB+rN1GRi2hbLcFaYs4PwVA5diLeAJH0gszv3x4Hr/S87MfbKQ==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==" + }, + "@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, + "@mongodb-js/saslprep": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", + "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "optional": true + }, + "@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "optional": true + }, + "@smithy/abort-controller": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", + "integrity": "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "requires": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/config-resolver": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.0.1.tgz", + "integrity": "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==", + "requires": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "tslib": "^2.6.2" + } + }, + "@smithy/core": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.4.tgz", + "integrity": "sha512-wFExFGK+7r2wYriOqe7RRIBNpvxwiS95ih09+GSLRBdoyK/O1uZA7K7pKesj5CBvwJuSBeXwLyR88WwIAY+DGA==", + "requires": { + "@smithy/middleware-serde": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-stream": "^4.1.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/credential-provider-imds": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.1.tgz", + "integrity": "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==", + "requires": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "tslib": "^2.6.2" + } + }, + "@smithy/eventstream-codec": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.1.tgz", + "integrity": "sha512-Q2bCAAR6zXNVtJgifsU16ZjKGqdw/DyecKNgIgi7dlqw04fqDu0mnq+JmGphqheypVc64CYq3azSuCpAdFk2+A==", + "requires": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.1.0", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/eventstream-serde-browser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.1.tgz", + "integrity": "sha512-HbIybmz5rhNg+zxKiyVAnvdM3vkzjE6ccrJ620iPL8IXcJEntd3hnBl+ktMwIy12Te/kyrSbUb8UCdnUT4QEdA==", + "requires": { + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/eventstream-serde-config-resolver": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.0.1.tgz", + "integrity": "sha512-lSipaiq3rmHguHa3QFF4YcCM3VJOrY9oq2sow3qlhFY+nBSTF/nrO82MUQRPrxHQXA58J5G1UnU2WuJfi465BA==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/eventstream-serde-node": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.1.tgz", + "integrity": "sha512-o4CoOI6oYGYJ4zXo34U8X9szDe3oGjmHgsMGiZM0j4vtNoT+h80TLnkUcrLZR3+E6HIxqW+G+9WHAVfl0GXK0Q==", + "requires": { + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/eventstream-serde-universal": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.1.tgz", + "integrity": "sha512-Z94uZp0tGJuxds3iEAZBqGU2QiaBHP4YytLUjwZWx+oUeohCsLyUm33yp4MMBmhkuPqSbQCXq5hDet6JGUgHWA==", + "requires": { + "@smithy/eventstream-codec": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.1.tgz", + "integrity": "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==", + "requires": { + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/hash-blob-browser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.1.tgz", + "integrity": "sha512-rkFIrQOKZGS6i1D3gKJ8skJ0RlXqDvb1IyAphksaFOMzkn3v3I1eJ8m7OkLj0jf1McP63rcCEoLlkAn/HjcTRw==", + "requires": { + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/hash-node": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.1.tgz", + "integrity": "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==", + "requires": { + "@smithy/types": "^4.1.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "requires": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/hash-stream-node": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.1.tgz", + "integrity": "sha512-U1rAE1fxmReCIr6D2o/4ROqAQX+GffZpyMt3d7njtGDr2pUNmAKRWa49gsNVhCh2vVAuf3wXzWwNr2YN8PAXIw==", + "requires": { + "@smithy/types": "^4.1.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/invalid-dependency": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.1.tgz", + "integrity": "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/md5-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.1.tgz", + "integrity": "sha512-HLZ647L27APi6zXkZlzSFZIjpo8po45YiyjMGJZM3gyDY8n7dPGdmxIIljLm4gPt/7rRvutLTTkYJpZVfG5r+A==", + "requires": { + "@smithy/types": "^4.1.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-content-length": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.1.tgz", + "integrity": "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==", + "requires": { + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.5.tgz", + "integrity": "sha512-cPzGZV7qStHwboFrm6GfrzQE+YDiCzWcTh4+7wKrP/ZQ4gkw+r7qDjV8GjM4N0UYsuUyLfpzLGg5hxsYTU11WA==", + "requires": { + "@smithy/core": "^3.1.4", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-middleware": "^4.0.1", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.6.tgz", + "integrity": "sha512-s8QzuOQnbdvRymD9Gt9c9zMq10wUQAHQ3z72uirrBHCwZcLTrL5iCOuVTMdka2IXOYhQE890WD5t6G24+F+Qcg==", + "requires": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "dependencies": { + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, + "@smithy/middleware-serde": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.2.tgz", + "integrity": "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.1.tgz", + "integrity": "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-config-provider": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.1.tgz", + "integrity": "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==", + "requires": { + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.2.tgz", + "integrity": "sha512-X66H9aah9hisLLSnGuzRYba6vckuFtGE+a5DcHLliI/YlqKrGoxhisD5XbX44KyoeRzoNlGr94eTsMVHFAzPOw==", + "requires": { + "@smithy/abort-controller": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/property-provider": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.1.tgz", + "integrity": "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.0.1.tgz", + "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.1.tgz", + "integrity": "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==", + "requires": { + "@smithy/types": "^4.1.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.1.tgz", + "integrity": "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/service-error-classification": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.1.tgz", + "integrity": "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==", + "requires": { + "@smithy/types": "^4.1.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.1.tgz", + "integrity": "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/signature-v4": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.1.tgz", + "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", + "requires": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "requires": { + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/smithy-client": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.5.tgz", + "integrity": "sha512-DMXYoYeL4QkElr216n1yodTFeATbfb4jwYM9gKn71Rw/FNA1/Sm36tkTSCsZEs7mgpG3OINmkxL9vgVFzyGPaw==", + "requires": { + "@smithy/core": "^3.1.4", + "@smithy/middleware-endpoint": "^4.0.5", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.1.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", + "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", + "integrity": "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==", + "requires": { + "@smithy/querystring-parser": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "requires": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "requires": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-defaults-mode-browser": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.6.tgz", + "integrity": "sha512-N8+VCt+piupH1A7DgSVDNrVHqRLz8r6DvBkpS7EWHiIxsUk4jqGuQLjqC/gnCzmwGkVBdNruHoYAzzaSQ8e80w==", + "requires": { + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-defaults-mode-node": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.6.tgz", + "integrity": "sha512-9zhx1shd1VwSSVvLZB8CM3qQ3RPD3le7A3h/UPuyh/PC7g4OaWDi2xUNzamsVoSmCGtmUBONl56lM2EU6LcH7A==", + "requires": { + "@smithy/config-resolver": "^4.0.1", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.5", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-endpoints": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.1.tgz", + "integrity": "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==", + "requires": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", + "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", + "requires": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-retry": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.1.tgz", + "integrity": "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==", + "requires": { + "@smithy/service-error-classification": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.1.tgz", + "integrity": "sha512-+Xvh8nhy0Wjv1y71rBVyV3eJU3356XsFQNI8dEZVNrQju7Eib8G31GWtO+zMa9kTCGd41Mflu+ZKfmQL/o2XzQ==", + "requires": { + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.2", + "@smithy/types": "^4.1.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "requires": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "requires": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "requires": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/util-waiter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.2.tgz", + "integrity": "sha512-piUTHyp2Axx3p/kc2CIJkYSv0BAaheBQmbACZgQSSfWUumWNW+R1lL+H9PDBxKJkvOeEX+hKYEFiwO8xagL8AQ==", + "requires": { + "@smithy/abort-controller": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + } + }, + "@types/node": { + "version": "22.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", + "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", + "requires": { + "undici-types": "~6.20.0" + } + }, + "@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "array-parallel": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz", + "integrity": "sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==" + }, + "array-series": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz", + "integrity": "sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==" + }, + "async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "aws-sdk": { + "version": "2.1692.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1692.0.tgz", + "integrity": "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.6.2" + }, + "dependencies": { + "uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" + } + } + }, + "axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true + }, + "body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==" + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" + }, + "bull": { + "version": "4.16.5", + "resolved": "https://registry.npmjs.org/bull/-/bull-4.16.5.tgz", + "integrity": "sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ==", + "requires": { + "cron-parser": "^4.9.0", + "get-port": "^5.1.1", + "ioredis": "^5.3.2", + "lodash": "^4.17.21", + "msgpackr": "^1.11.2", + "semver": "^7.5.2", + "uuid": "^8.3.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + } + }, + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" + }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "requires": { + "luxon": "^3.2.1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true + }, + "dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==" + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "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" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==" + }, + "express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "requires": { + "ms": "^2.1.3" + } + } + } + }, + "fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "requires": { + "strnum": "^1.0.5" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "requires": { + "pend": "~1.2.0" + } + }, + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "requires": { + "is-callable": "^1.2.7" + } + }, + "form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==" + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "gm": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/gm/-/gm-1.25.0.tgz", + "integrity": "sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==", + "requires": { + "array-parallel": "~0.1.3", + "array-series": "~0.1.5", + "cross-spawn": "^4.0.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ioredis": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.5.0.tgz", + "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==", + "requires": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "requires": { + "ms": "^2.1.3" + } + } + } + }, + "ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + } + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + } + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "requires": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "requires": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "requires": { + "which-typed-array": "^1.1.16" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" + }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==" + }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "requires": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "luxon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==" + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "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==" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "requires": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + } + }, + "mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "mongoose": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.6.tgz", + "integrity": "sha512-1oVPRHvcmPVwk/zeSTEzayzQEVeYQM1D5zrkLsttfNNB7pPRUmkKeFu6gpbvyEswOuZLrWJjqB8kSTY+k2AZOA==", + "requires": { + "bson": "^5.5.0", + "kareem": "2.5.1", + "mongodb": "5.9.2", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + } + }, + "mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==" + }, + "mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "requires": { + "debug": "4.x" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "requires": { + "ms": "^2.1.3" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "msgpackr": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", + "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", + "requires": { + "msgpackr-extract": "^3.0.2" + } + }, + "msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "optional": true, + "requires": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3", + "node-gyp-build-optional-packages": "5.2.2" + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "optional": true, + "requires": { + "detect-libc": "^2.0.1" + } + }, + "nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "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" + } + }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, + "p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "requires": { + "yocto-queue": "^1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "pdf2pic": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/pdf2pic/-/pdf2pic-2.2.4.tgz", + "integrity": "sha512-FoimNRDKblUT2p0WpX5vRvcXlyNs9EYRxonpUHGzbCHJ6o+pJIfcW2YREzCqTeOUdD9RoGP7V565oGGIG1NdEg==", + "requires": { + "fs-extra": "^10.0.0", + "gm": "^1.23.1" + }, + "dependencies": { + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==" + }, + "proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "requires": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + }, + "qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "requires": { + "side-channel": "^1.0.6" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "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" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "requires": { + "redis-errors": "^1.0.0" + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + } + }, + "safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + }, + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + } + } + }, + "serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + } + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, + "sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "requires": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + } + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" + }, + "standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "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" + } + }, + "strnum": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.1.tgz", + "integrity": "sha512-O7aCHfYCamLCctjAiaucmE+fHf2DYHkus2OKCn4Wv03sykfFtgeECn505X6K4mPl8CRNd/qurC9guq+ynoN4pw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + } + } + }, + "triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==" + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "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", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + } + }, + "winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "requires": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + } + }, + "winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "requires": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==" + } + } +} diff --git a/Utilities/image_answering_utility/package.json b/Utilities/image_answering_utility/package.json new file mode 100644 index 00000000..1bedb518 --- /dev/null +++ b/Utilities/image_answering_utility/package.json @@ -0,0 +1,35 @@ +{ + "name": "pdf-processor", + "version": "1.0.0", + "description": "PDF processing service", + "main": "src/api/server.js", + "scripts": { + "setup": "npm install && node src/scripts/install-mongodb.js && node src/scripts/init-mongodb.js", + "start": "node src/api/server.js", + "dev": "nodemon src/api/server.js", + "init-mongodb": "node src/scripts/init-mongodb.js", + "install-mongodb": "node src/scripts/install-mongodb.js" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.750.0", + "@aws-sdk/s3-request-presigner": "^3.750.0", + "aws-sdk": "^2.1692.0", + "axios": "^1.7.9", + "bull": "^4.16.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "extract-zip": "^2.0.1", + "form-data": "^4.0.2", + "fs-extra": "^11.3.0", + "ioredis": "^5.5.0", + "mongodb": "^5.0.0", + "mongoose": "^7.0.0", + "p-limit": "^6.2.0", + "pdf2pic": "^2.2.4", + "proper-lockfile": "^4.1.2", + "winston": "^3.x" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } +} diff --git a/Utilities/image_answering_utility/setup/start_api_service.sh b/Utilities/image_answering_utility/setup/start_api_service.sh new file mode 100644 index 00000000..860fb369 --- /dev/null +++ b/Utilities/image_answering_utility/setup/start_api_service.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +# Start API Service Script +echo "Starting API Service..." + +# Check for nvm and install/use Node.js v16 +if [ -f "$HOME/.nvm/nvm.sh" ]; then + # Load nvm + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + + # Install Node.js v16 if not already installed + if ! nvm list | grep -q "v16"; then + echo "Installing Node.js v16..." + nvm install 16 + fi + + # Use Node.js v16 + echo "Switching to Node.js v16..." + nvm use 16 +else + # Check if node version is less than 16 + NODE_VERSION=$(node --version | cut -d. -f1 | tr -d 'v') + if [ "$NODE_VERSION" -lt "16" ]; then + echo "Error: Node.js version 16 or higher is required." + echo "Please install nvm or upgrade Node.js manually." + exit 1 + fi +fi + +# Navigate to project root +cd "$(dirname "$0")/.." + +# Check if node_modules exists and run setup if needed +if [ ! -d "node_modules" ]; then + echo "Running initial setup..." + npm run setup +else + # Check MongoDB installation + if ! command -v mongod &> /dev/null; then + echo "MongoDB not found. Installing MongoDB..." + npm run install-mongodb + fi +fi + +# Check if MongoDB is running +if ! pgrep mongod > /dev/null; then + echo "MongoDB is not running. Starting MongoDB..." + sudo systemctl start mongod + sleep 5 # Wait for MongoDB to start +fi + +# Initialize MongoDB if needed +echo "Initializing MongoDB..." +npm run init-mongodb + +# Check if Redis is running +if ! pgrep redis-server > /dev/null; then + echo "Redis server is not running. Starting Redis..." + redis-server & + sleep 2 +fi + +# Create required directories +mkdir -p logs storage/processing + +# Check if GraphicsMagick is installed +if ! command -v gm &> /dev/null; then + echo "GraphicsMagick is not installed. Please install it first:" + echo "Ubuntu/Debian: sudo apt-get install graphicsmagick" + echo "macOS: brew install graphicsmagick" + echo "Windows: Download from https://sourceforge.net/projects/graphicsmagick/" + exit 1 +fi + +# Check if config/.env exists +if [ ! -f "config/.env" ]; then + echo "Error: config/.env file not found!" + echo "Please create config/.env file with your configuration settings." + exit 1 +fi + +# Start the API service +echo "Starting API service..." +node src/api/server.js diff --git a/Utilities/image_answering_utility/setup/start_callback_service.sh b/Utilities/image_answering_utility/setup/start_callback_service.sh new file mode 100644 index 00000000..7ae39157 --- /dev/null +++ b/Utilities/image_answering_utility/setup/start_callback_service.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Start Callback Service Script +echo "Starting Callback Service..." + +# Navigate to project root +cd "$(dirname "$0")/.." +PROJECT_ROOT=$(pwd) + +# Check if Python virtual environment exists +if [ ! -d "venv" ]; then + echo "Creating Python virtual environment..." + python3 -m venv venv +fi + +# Activate virtual environment +source venv/bin/activate + +# Install Python dependencies for callback service +echo "Installing Python dependencies..." +cd python +pip install -r requirements.txt +cd .. # Return to project root + +# Create logs directory if it doesn't exist +mkdir -p logs + +# Create symbolic link for logs directory in python directory if it doesn't exist +if [ ! -L "python/logs" ] && [ ! -d "python/logs" ]; then + ln -s ../logs python/logs +fi + +# Start the Callback service +echo "Starting Callback service..." +export PYTHONPATH="${PROJECT_ROOT}:${PYTHONPATH}" +python python/services/callback_server.py \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/api/server.js b/Utilities/image_answering_utility/src/api/server.js new file mode 100644 index 00000000..17f1cd0c --- /dev/null +++ b/Utilities/image_answering_utility/src/api/server.js @@ -0,0 +1,508 @@ +const express = require('express'); +const ProcessingController = require('../services/pdf-processing-service/controllers/ProcessingController'); +const { pdfQueue } = require('../config/queue-config'); +const { createLogger } = require('../utils/logger'); +const path = require('path'); +const axios = require('axios'); +const fs = require('fs').promises; +const fsSync = require('fs'); +const { promisify } = require('util'); +const stream = require('stream'); +const pipeline = promisify(stream.pipeline); +const extract = require('extract-zip'); +const { exec } = require('child_process'); +const util = require('util'); +const execAsync = util.promisify(exec); +const { createWriteStream } = require('fs'); +require('dotenv').config({ path: path.join(__dirname, '../../config/.env') }); +const ensureLogsDirectory = require('../utils/ensure-logs-dir'); +const { StorageManager } = require('../services/pdf-processing-service/utils/StorageManager'); +const os = require('os'); +const { FileRecord } = require('../services/pdf-processing-service/models/FileRecord'); +const mongoose = require('mongoose'); +const { MongoManager } = require('../services/pdf-processing-service/utils/MongoManager'); + +// Initialize controllers and utilities +const processingController = new ProcessingController(); +const storageManager = new StorageManager(); + +// Ensure logs directory exists +ensureLogsDirectory(); + +const app = express(); +app.use(express.json()); + +// Setup logger for API service +const logger = createLogger('api-service'); + +// Replace hardcoded values with environment variables +// const DOWNLOADS_DIR = path.join(__dirname, '../../', process.env.DOWNLOADS_DIR); +const PORT = process.env.PORT || 3000; + +// // Create downloads directory if it doesn't exist +// if (!fsSync.existsSync(DOWNLOADS_DIR)) { +// fsSync.mkdirSync(DOWNLOADS_DIR, { recursive: true }); +// } + +// Add MongoDB connection +MongoManager.connect() + .then(() => { + app.listen(PORT, () => { + logger.info(`Server is running on port ${PORT}`); + }); + }) + .catch(err => { + logger.error('Failed to initialize MongoDB:', err); + // Still start the server even if MongoDB fails + app.listen(PORT, () => { + logger.info(`Server is running on port ${PORT} (without MongoDB)`); + }); + }); + +// Set up periodic cleanup of old directories (every 1 hour) +setInterval(async () => { + try { + await storageManager.cleanupOldDirectories(); + } catch (error) { + logger.error(`Error during periodic cleanup: ${error.message}`); + } +}, 60 * 60 * 1000); // 1 hour + +// Update the processDirectoryFromUrl function to use the functions from queue-config +async function processDirectoryFromUrl(downloadUrl, include_base64, uniqueId) { + logger.info(`Processing URL with uniqueId: ${uniqueId}`); + const tempDir = await storageManager.createTempDirectory(uniqueId); + + try { + // Clean the filename by removing query parameters + const url = new URL(downloadUrl); + const originalFileName = path.basename(url.pathname); + const cleanFileName = originalFileName.split('?')[0]; + + // Download file to temp directory with clean filename + const downloadedFile = path.join(tempDir, cleanFileName); + + // Use the functions from queue-config module + const { downloadFile, isArchive, extractInPlace } = require('../config/queue-config'); + await downloadFile(downloadUrl, downloadedFile); + + // If it's an archive, extract in the same directory + if (isArchive(downloadedFile)) { + await extractInPlace(downloadedFile, tempDir); + await fs.unlink(downloadedFile); + } + + // Retrieve all PDF files in the temp directory + const pdfFiles = await storageManager.getAllPDFFiles(tempDir); + logger.info(`Found ${pdfFiles.length} PDF files in temp directory for uniqueId: ${uniqueId}`); + + let results; + if (pdfFiles.length === 1) { + const singlePDFPath = pdfFiles[0]; + logger.info(`Processing single PDF: ${path.basename(singlePDFPath)} with uniqueId: ${uniqueId}`); + const singleResult = await processingController.processSinglePDF(singlePDFPath, { + include_base64, + uniqueId + }); + results = [singleResult]; + } else { + logger.info(`Processing multiple PDFs with uniqueId: ${uniqueId}`); + results = await processingController.processDirectory(tempDir, { + include_base64, + uniqueId + }); + } + + return results; + } finally { + await storageManager.cleanup(tempDir); + } +} + +// Track active processing count +let activeProcessingCount = 0; + +// Function to check if system is overloaded +function isSystemOverloaded() { + const MAX_CONCURRENT = parseInt(process.env.MAX_CONCURRENT_PROCESSING, 10) || 3; + return activeProcessingCount >= MAX_CONCURRENT; +} + +// Function to get accurate queue position +async function getQueuePosition(jobId) { + const [activeJobs, waitingJobs] = await Promise.all([ + pdfQueue.getActive(), + pdfQueue.getWaiting() + ]); + + // Find position in waiting queue + const position = waitingJobs.findIndex(job => job.id === jobId); + + if (position === -1) { + // If not found in waiting queue, it might be active or completed + return activeJobs.findIndex(job => job.id === jobId); + } + + // Position is number of active jobs plus position in waiting queue + return activeJobs.length + position; +} + +// Middleware to log requests +app.use((req, res, next) => { + logger.info(`${req.method} ${req.path} - ${req.ip}`); + next(); +}); + +// Health check endpoint +app.get('/health', (req, res) => { + res.json({ status: 'healthy' }); +}); + +// Modified processing endpoint with queue support +app.post('/process-directory-from-url', async (req, res) => { + const startTime = new Date(); + logger.info(`Request started at: ${startTime.toISOString()}`); + + try { + const { downloadUrl, include_base64 = false } = req.body; + + if (!downloadUrl) { + return res.status(400).json({ + status: 'error', + message: 'Download URL is required' + }); + } + + // Generate a unique ID that includes both timestamp and URL info + const urlObj = new URL(downloadUrl); + const filename = path.basename(urlObj.pathname).split('?')[0]; + const timestamp = Date.now(); + const uniqueId = `${filename.replace(/[^a-zA-Z0-9]/g, '_')}_${timestamp}`; + + // Create initial database entry with received status + try { + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + uniqueId, + filename, + status: 'received', + startTime: new Date(), + lastUpdated: new Date() + }, + { upsert: true, new: true } + ); + logger.info(`Created initial status entry for ${uniqueId} with status: received`); + } catch (error) { + logger.error(`Failed to create initial status for ${uniqueId}: ${error.message}`); + return res.status(500).json({ + status: 'error', + message: 'Failed to initialize request tracking' + }); + } + + // Check if system is overloaded + if (isSystemOverloaded()) { + logger.info(`System overloaded, queueing request for ${downloadUrl}`); + + // Create initial entry with queued status + try { + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + uniqueId, + filename, + status: 'queued', + startTime: new Date(), + lastUpdated: new Date() + }, + { upsert: true, new: true } + ); + logger.info(`Created queued status entry for ${uniqueId}`); + } catch (error) { + logger.error(`Failed to create queue status for ${uniqueId}: ${error.message}`); + return res.status(500).json({ + status: 'error', + message: 'Failed to initialize request tracking' + }); + } + + // Add to queue with uniqueId + const job = await pdfQueue.add({ + downloadUrl, + include_base64, + uniqueId, + type: 'url' // Add type to distinguish URL processing + }, { + attempts: 3, + backoff: { + type: 'exponential', + delay: 5000 + } + }); + + // Get accurate queue position + const [queuePosition, counts] = await Promise.all([ + getQueuePosition(job.id), + pdfQueue.getJobCounts() + ]); + + return res.status(202).json({ + status: 'queued', + message: 'Request queued for processing', + jobId: job.id, + queuePosition: queuePosition, + totalJobs: counts.active + counts.waiting, + uniqueId + }); + } + + // If system not overloaded, process immediately + activeProcessingCount++; + logger.info(`Starting download from: ${downloadUrl}`); + + // Send acknowledgment with uniqueId + res.status(202).json({ + status: 'processing', + message: 'PDF processing started', + uniqueId + }); + + try { + const results = await processDirectoryFromUrl(downloadUrl, include_base64, uniqueId); + logger.info(`Processing completed for ${downloadUrl}`); + logger.info(`Successful: ${results.filter(r => r.success).length}`); + logger.info(`Failed: ${results.filter(r => !r.success).length}`); + } finally { + activeProcessingCount--; + const endTime = new Date(); + const processingTime = (endTime - startTime) / 1000; // in seconds + logger.info(`Request completed at: ${endTime.toISOString()}`); + logger.info(`Total processing time: ${processingTime.toFixed(2)} seconds`); + } + + } catch (error) { + logger.error('Error in process-directory-from-url:', error); + if (!res.headersSent) { + res.status(500).json({ + status: 'error', + message: error.message + }); + } + } +}); + +// Add queue status endpoint +app.get('/queue-status', async (req, res) => { + try { + const counts = await pdfQueue.getJobCounts(); + const activeJobs = await pdfQueue.getActive(); + const waitingJobs = await pdfQueue.getWaiting(); + + res.status(200).json({ + status: 'success', + activeProcessing: activeProcessingCount, + queueCounts: counts, + activeJobs: activeJobs.map(job => ({ + id: job.id, + timestamp: job.timestamp, + progress: job.progress() + })), + waitingJobs: waitingJobs.map(job => ({ + id: job.id, + timestamp: job.timestamp + })) + }); + } catch (error) { + res.status(500).json({ + status: 'error', + message: error.message + }); + } +}); + +// Add file status endpoint for individual file tracking +app.get('/file-status/:uniqueId', async (req, res) => { + try { + const { uniqueId } = req.params; + + const fileStatus = await FileRecord.findOne({ uniqueId }); + + if (!fileStatus) { + return res.status(404).json({ + status: 'error', + message: 'File status not found' + }); + } + + res.status(200).json({ + status: 'success', + data: { + uniqueId: fileStatus.uniqueId, + filename: fileStatus.filename, + status: fileStatus.status, + s3Url: fileStatus.s3Url, + error: fileStatus.error, + startTime: fileStatus.startTime, + lastUpdated: fileStatus.lastUpdated, + completedTime: fileStatus.completedTime + } + }); + } catch (error) { + logger.error('Error fetching file status:', error); + res.status(500).json({ + status: 'error', + message: 'Internal server error' + }); + } +}); + +// Add endpoint for processing local directory +app.post('/process-local-directory', async (req, res) => { + const startTime = new Date(); + logger.info(`Request started at: ${startTime.toISOString()}`); + + try { + const { directoryPath, include_base64 = false } = req.body; + + if (!directoryPath) { + return res.status(400).json({ + status: 'error', + message: 'Directory path is required' + }); + } + + // Validate if directory exists + try { + const stats = await fs.stat(directoryPath); + if (!stats.isDirectory()) { + return res.status(400).json({ + status: 'error', + message: 'Provided path is not a directory' + }); + } + } catch (error) { + return res.status(400).json({ + status: 'error', + message: `Directory does not exist or is not accessible: ${error.message}` + }); + } + + // Generate unique ID + const uniqueId = `local_${Date.now()}`; + + // Create initial database entry with received status + try { + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + uniqueId, + filename: path.basename(directoryPath), + status: 'received', + startTime: new Date(), + lastUpdated: new Date() + }, + { upsert: true, new: true } + ); + logger.info(`Created initial status entry for ${uniqueId} with status: received`); + } catch (error) { + logger.error(`Failed to create initial status for ${uniqueId}: ${error.message}`); + return res.status(500).json({ + status: 'error', + message: 'Failed to initialize request tracking' + }); + } + + // Check if system is overloaded + if (isSystemOverloaded()) { + logger.info(`System overloaded, queueing request for directory: ${directoryPath}`); + + // Create initial entry with queued status + try { + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + uniqueId, + filename: path.basename(directoryPath), + status: 'queued', + startTime: new Date(), + lastUpdated: new Date() + }, + { upsert: true, new: true } + ); + logger.info(`Created queued status entry for ${uniqueId}`); + } catch (error) { + logger.error(`Failed to create queue status for ${uniqueId}: ${error.message}`); + return res.status(500).json({ + status: 'error', + message: 'Failed to initialize request tracking' + }); + } + + // Add to queue + const job = await pdfQueue.add({ + type: 'local_directory', + directoryPath, + include_base64, + uniqueId + }, { + attempts: 3, + backoff: { + type: 'exponential', + delay: 5000 + } + }); + + return res.status(202).json({ + status: 'queued', + message: 'Request queued for processing', + jobId: job.id, + queuePosition: await pdfQueue.getJobCounts().then(counts => counts.waiting), + uniqueId + }); + } + + // If system not overloaded, process immediately + activeProcessingCount++; + logger.info(`Starting processing for directory: ${directoryPath}`); + + res.status(202).json({ + status: 'processing', + message: 'Directory processing started', + uniqueId + }); + + try { + const results = await processingController.processDirectory(directoryPath, { + include_base64, + uniqueId + }); + + logger.info(`Processing completed for directory: ${directoryPath}`); + logger.info(`Successful: ${results.filter(r => r.success).length}`); + logger.info(`Failed: ${results.filter(r => !r.success).length}`); + + } finally { + activeProcessingCount--; + const endTime = new Date(); + const processingTime = (endTime - startTime) / 1000; // in seconds + logger.info(`Request completed at: ${endTime.toISOString()}`); + logger.info(`Total processing time: ${processingTime.toFixed(2)} seconds`); + } + + } catch (error) { + logger.error('Error in process-local-directory:', error); + if (!res.headersSent) { + res.status(500).json({ + status: 'error', + message: error.message + }); + } + } +}); + +// Error handling middleware +app.use((err, req, res, next) => { + logger.error(`Error: ${err.message}`); + res.status(500).json({ error: 'Internal Server Error' }); +}); diff --git a/Utilities/image_answering_utility/src/config/aws-config.js b/Utilities/image_answering_utility/src/config/aws-config.js new file mode 100644 index 00000000..b9e75be5 --- /dev/null +++ b/Utilities/image_answering_utility/src/config/aws-config.js @@ -0,0 +1,62 @@ +const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3'); +const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); +const path = require('path'); +require('dotenv').config({ path: path.join(__dirname, '../../config/.env') }); + +// Create an S3 client +const s3 = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY + }, + maxAttempts: 3, + forcePathStyle: true // Use path-style URLs for S3 +}); + +const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME; +const S3_FOLDER_PATH = process.env.S3_FOLDER_PATH || ''; + +// Function to generate presigned URL for getting objects +async function generatePresignedUrl(key) { + try { + const command = new GetObjectCommand({ + Bucket: S3_BUCKET_NAME, + Key: key + }); + return await getSignedUrl(s3, command, { + expiresIn: parseInt(process.env.S3_URL_EXPIRY, 10) || 604800 + }); + } catch (error) { + console.error(`Error generating presigned URL: ${error.message}`); + throw error; + } +} + +// Function to upload data to S3 +async function uploadToS3(key, data) { + try { + const s3Key = path.join(S3_FOLDER_PATH, key).replace(/\\/g, '/'); + const putCommand = new PutObjectCommand({ + Bucket: S3_BUCKET_NAME, + Key: s3Key, + Body: typeof data === 'string' ? data : JSON.stringify(data), + ContentType: 'application/json' + }); + + await s3.send(putCommand); + + // Generate presigned URL for the uploaded object + return await generatePresignedUrl(s3Key); + } catch (error) { + console.error(`Error uploading to S3: ${error.message}`); + throw error; + } +} + +module.exports = { + s3, + S3_BUCKET_NAME, + uploadToS3, + generatePresignedUrl +}; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/config/mongodb-config.js b/Utilities/image_answering_utility/src/config/mongodb-config.js new file mode 100644 index 00000000..d69681dc --- /dev/null +++ b/Utilities/image_answering_utility/src/config/mongodb-config.js @@ -0,0 +1,9 @@ +require('dotenv').config(); + +module.exports = { + uri: process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/pdf-processor', + options: { + useNewUrlParser: true, + useUnifiedTopology: true + } +}; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/config/queue-config.js b/Utilities/image_answering_utility/src/config/queue-config.js new file mode 100644 index 00000000..aae657cf --- /dev/null +++ b/Utilities/image_answering_utility/src/config/queue-config.js @@ -0,0 +1,228 @@ +const Queue = require('bull'); +const ProcessingController = require('../services/pdf-processing-service/controllers/ProcessingController'); +const { StorageManager } = require('../services/pdf-processing-service/utils/StorageManager'); +const { createLogger } = require('../utils/logger'); +const axios = require('axios'); +const path = require('path'); +const { promisify } = require('util'); +const stream = require('stream'); +const pipeline = promisify(stream.pipeline); +const { createWriteStream } = require('fs'); +const fsSync = require('fs'); +const extract = require('extract-zip'); +const { exec } = require('child_process'); +const util = require('util'); +const execAsync = util.promisify(exec); +const fs = require('fs').promises; +require('dotenv').config({ path: path.join(__dirname, '../../config/.env') }); +const ensureLogsDirectory = require('../utils/ensure-logs-dir'); + +// Initialize controllers and utilities +const processingController = new ProcessingController(); +const storageManager = new StorageManager(); + +// Ensure logs directory exists +ensureLogsDirectory(); + +// Add DOWNLOADS_DIR constant at the top level +// const DOWNLOADS_DIR = path.join(__dirname, '../../', process.env.DOWNLOADS_DIR); + +// Setup logger for queue service +const logger = createLogger('queue-service'); + +// Helper function to check if file is an archive +function isArchive(filePath) { + const ext = path.extname(filePath).toLowerCase(); + return ['.zip', '.tar', '.gz', '.tgz'].includes(ext); +} + +// Helper function to extract archive +async function extractInPlace(archivePath, extractDir) { + const ext = path.extname(archivePath).toLowerCase(); + + if (ext === '.zip') { + await extract(archivePath, { dir: extractDir }); + } else if (['.tar', '.gz', '.tgz'].includes(ext)) { + await execAsync(`tar -xf "${archivePath}" -C "${extractDir}"`); + } +} + +// Helper function to download file +async function downloadFile(url, outputPath) { + try { + const response = await axios({ + method: 'GET', + url: url, + responseType: 'stream', + maxBodyLength: Infinity, + maxContentLength: Infinity, + timeout: 0 + }); + + await pipeline(response.data, createWriteStream(outputPath)); + logger.info(`File downloaded to: ${outputPath}`); + } catch (error) { + logger.error(`Error downloading file: ${error.message}`); + throw error; + } +} + +// Create a processing queue with concurrency limit +const pdfQueue = new Queue('pdf-processing', { + redis: { + host: process.env.REDIS_HOST, + port: parseInt(process.env.REDIS_PORT, 10) + } +}); + +// Process URL type jobs +async function processUrlJob(job) { + const { downloadUrl, include_base64, uniqueId } = job.data; + + // Log the uniqueId we're processing to track it + logger.info(`Processing queued URL job with uniqueId: ${uniqueId}`); + + // Update status to processing in database before starting actual work + try { + const { FileRecord } = require('../services/pdf-processing-service/models/FileRecord'); + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + status: 'processing', + lastUpdated: new Date() + }, + { new: true } + ); + logger.info(`Updated status to processing for queued job with uniqueId: ${uniqueId}`); + } catch (error) { + logger.error(`Failed to update status for ${uniqueId}: ${error.message}`); + } + + const tempDir = await storageManager.createTempDirectory(uniqueId); + + try { + // Clean the filename + const url = new URL(downloadUrl); + const cleanFileName = path.basename(url.pathname).split('?')[0]; + const downloadedFile = path.join(tempDir, cleanFileName); + + await downloadFile(downloadUrl, downloadedFile); + + if (isArchive(downloadedFile)) { + await extractInPlace(downloadedFile, tempDir); + await fs.unlink(downloadedFile); + } + + // Pass the original uniqueId explicitly + const pdfFiles = await storageManager.getAllPDFFiles(tempDir); + + let results; + if (pdfFiles.length === 1) { + const singlePDFPath = pdfFiles[0]; + const singleResult = await processingController.processSinglePDF(singlePDFPath, { + include_base64, + uniqueId // Ensure uniqueId is passed correctly + }); + results = [singleResult]; + } else { + results = await processingController.processDirectory(tempDir, { + include_base64, + uniqueId // Ensure uniqueId is passed correctly + }); + } + + job.progress(100); + + logger.info(`Completed URL job with uniqueId: ${uniqueId}`); + return { + status: 'completed', + uniqueId, // Return the uniqueId in the result + successful: results.filter(r => r.success).length, + failed: results.filter(r => !r.success).length + }; + } finally { + await storageManager.cleanup(tempDir); + } +} + +// Process local directory type jobs +async function processLocalDirectoryJob(job) { + const { directoryPath, include_base64, uniqueId } = job.data; + + // Log the uniqueId we're processing to track it + logger.info(`Processing queued local directory job with uniqueId: ${uniqueId}`); + + // Update status to processing in database before starting actual work + try { + const { FileRecord } = require('../services/pdf-processing-service/models/FileRecord'); + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + status: 'processing', + lastUpdated: new Date() + }, + { new: true } + ); + logger.info(`Updated status to processing for queued job with uniqueId: ${uniqueId}`); + } catch (error) { + logger.error(`Failed to update status for ${uniqueId}: ${error.message}`); + } + + const results = await processingController.processDirectory(directoryPath, { + include_base64, + uniqueId // Ensure uniqueId is passed correctly + }); + + job.progress(100); + + logger.info(`Completed local directory job with uniqueId: ${uniqueId}`); + return { + status: 'completed', + uniqueId, // Return the uniqueId in the result + successful: results.filter(r => r.success).length, + failed: results.filter(r => !r.success).length + }; +} + +// Set maximum concurrent jobs from environment variable +// But limit to 1 for PDF processing to ensure sequential processing +const MAX_CONCURRENCY = Math.min(parseInt(process.env.QUEUE_CONCURRENCY, 10) || 1, 1); +logger.info(`Setting queue concurrency to ${MAX_CONCURRENCY} to ensure sequential processing`); + +pdfQueue.process(MAX_CONCURRENCY, async (job) => { + try { + logger.info(`Processing queued job ${job.id}`); + + if (job.data.type === 'local_directory') { + return await processLocalDirectoryJob(job); + } else if (job.data.type === 'url') { + return await processUrlJob(job); + } else { + throw new Error(`Unknown job type: ${job.data.type}`); + } + } catch (error) { + logger.error(`Queue job ${job.id} failed: ${error.message}`); + throw error; + } +}); + +// Log queue events +pdfQueue.on('completed', (job, result) => { + logger.info(`Job ${job.id} completed. Success: ${result.successful}, Failed: ${result.failed}, UniqueId: ${result.uniqueId || job.data.uniqueId || 'unknown'}`); +}); + +pdfQueue.on('failed', (job, error) => { + logger.error(`Job ${job.id} failed: ${error.message}`); +}); + +pdfQueue.on('stalled', (job) => { + logger.warn(`Job ${job.id} stalled`); +}); + +// Export the utility functions along with pdfQueue +module.exports = { + pdfQueue, + downloadFile, + isArchive, + extractInPlace +}; diff --git a/Utilities/image_answering_utility/src/scripts/init-mongodb.js b/Utilities/image_answering_utility/src/scripts/init-mongodb.js new file mode 100644 index 00000000..e0d7186d --- /dev/null +++ b/Utilities/image_answering_utility/src/scripts/init-mongodb.js @@ -0,0 +1,49 @@ +const { exec } = require('child_process'); +const logger = require('../services/pdf-processing-service/utils/logger'); + +function checkMongoDBService() { + return new Promise((resolve, reject) => { + // Check if MongoDB service is running + exec('systemctl status mongodb || systemctl status mongod', (error, stdout, stderr) => { + if (error) { + logger.warn('MongoDB service is not running. Attempting to start...'); + startMongoDBService(resolve, reject); + } else { + logger.info('MongoDB service is running'); + resolve(true); + } + }); + }); +} + +function startMongoDBService(resolve, reject) { + exec('sudo systemctl start mongodb || sudo systemctl start mongod', (error, stdout, stderr) => { + if (error) { + logger.error('Failed to start MongoDB service. Please install MongoDB:'); + logger.error('sudo apt update'); + logger.error('sudo apt install -y mongodb'); + logger.error('sudo systemctl enable mongodb'); + logger.error('sudo systemctl start mongodb'); + reject(error); + } else { + logger.info('MongoDB service started successfully'); + resolve(true); + } + }); +} + +async function init() { + try { + await checkMongoDBService(); + logger.info('MongoDB initialization completed'); + } catch (error) { + logger.error('MongoDB initialization failed:', error); + process.exit(1); + } +} + +if (require.main === module) { + init(); +} + +module.exports = { init }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/scripts/install-mongodb.js b/Utilities/image_answering_utility/src/scripts/install-mongodb.js new file mode 100644 index 00000000..7b2a5003 --- /dev/null +++ b/Utilities/image_answering_utility/src/scripts/install-mongodb.js @@ -0,0 +1,61 @@ +const { exec } = require('child_process'); +const util = require('util'); +const execAsync = util.promisify(exec); +const logger = require('../services/pdf-processing-service/utils/logger'); + +async function installMongoDB() { + try { + // Import MongoDB public GPG key + logger.info('Importing MongoDB public GPG key...'); + await execAsync('curl -fsSL https://pgp.mongodb.com/server-6.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-6.0.gpg --dearmor'); + + // Create list file for MongoDB + logger.info('Creating MongoDB list file...'); + const release = await execAsync('lsb_release -c -s'); + const codename = release.stdout.trim(); + + const repoLine = `deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu ${codename}/mongodb-org/6.0 multiverse`; + await execAsync(`echo "${repoLine}" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list`); + + // Update package list + logger.info('Updating package list...'); + await execAsync('sudo apt-get update'); + + // Install MongoDB + logger.info('Installing MongoDB...'); + await execAsync('sudo apt-get install -y mongodb-org'); + + // Start MongoDB service + logger.info('Starting MongoDB service...'); + await execAsync('sudo systemctl start mongod'); + + // Enable MongoDB service + logger.info('Enabling MongoDB service...'); + await execAsync('sudo systemctl enable mongod'); + + logger.info('MongoDB installation completed successfully'); + + // Verify MongoDB is running + const status = await execAsync('sudo systemctl status mongod'); + logger.info('MongoDB Status:', status.stdout); + + } catch (error) { + logger.error('Error installing MongoDB:', error.message); + throw error; + } +} + +async function init() { + try { + await installMongoDB(); + } catch (error) { + logger.error('MongoDB installation failed'); + process.exit(1); + } +} + +if (require.main === module) { + init(); +} + +module.exports = { installMongoDB }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-image-service/README.md b/Utilities/image_answering_utility/src/services/pdf-image-service/README.md new file mode 100644 index 00000000..956b1448 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-image-service/README.md @@ -0,0 +1,128 @@ +# PDF Image Service + +A robust Node.js service for converting PDF files to high-quality images with AWS S3 integration. + +## Features + +- Convert PDF files to high-quality PNG images +- Concurrent processing with configurable limits +- Automatic AWS S3 upload with presigned URLs +- Optional base64 image data output +- Temporary file management and cleanup +- GraphicsMagick integration for image processing +- Comprehensive error handling and logging + +## Prerequisites + +- Node.js (v14 or higher recommended) +- GraphicsMagick (`gm`) must be installed on the system +- AWS S3 bucket and credentials configured +- Sufficient storage space for temporary file processing + +## Installation + +1. Install GraphicsMagick on your system: + bash + # For Ubuntu/Debian + sudo apt-get install graphicsmagick + + # For macOS + brew install graphicsmagick + + +2. Install Node.js dependencies: + bash + npm install + + +## Configuration + +### S3 Configuration + +Configure your AWS S3 settings in `config/s3Config.js`: +- Bucket name +- Region +- Access credentials +- Default folder path +- URL expiration time for presigned URLs + +### Environment Variables + +The service supports customization of PDF to image conversion settings through environment variables. Create a `.env` file in the `config` directory with the following variables: + + +# PDF & Image Processing Configuration +PDF_IMAGE_DENSITY=150 # DPI setting for image conversion +PDF_IMAGE_FORMAT=png # Output image format +PDF_IMAGE_WIDTH=1200 # Maximum width of output images +PDF_IMAGE_HEIGHT=1600 # Maximum height of output images +PDF_IMAGE_QUALITY=80 # Image quality (1-100) +PDF_PRESERVE_ASPECT_RATIO=true # Maintain original aspect ratio + + +All environment variables are optional and have default values: +- `PDF_IMAGE_DENSITY`: Default 150 DPI +- `PDF_IMAGE_FORMAT`: Default 'png' +- `PDF_IMAGE_WIDTH`: Default 1200px +- `PDF_IMAGE_HEIGHT`: Default 1600px +- `PDF_IMAGE_QUALITY`: Default 80 +- `PDF_PRESERVE_ASPECT_RATIO`: Default true + + + +### Response Format + +The service returns an array of processed pages with the following structure: + +javascript +[ + { + page_number: 1, + image_url: "https://s3-presigned-url...", + base64_data: "base64_string..." // if includeBase64 is true + }, + // ... additional pages +] + + +## Architecture + +The service is organized into several components: + +- **ImageController**: Main entry point for the service +- **PDFImageService**: Core conversion and processing logic +- **StorageManager**: Handles temporary file operations +- **S3Config**: AWS S3 configuration and client setup + +## Error Handling + +The service includes comprehensive error handling for: +- Missing dependencies +- File access issues +- PDF conversion failures +- S3 upload errors +- Cleanup operations + +All errors are logged using the built-in logger utility. + +## Performance + +- Configurable concurrency limit (default: 3 concurrent processes) +- Batch processing for memory efficiency +- Automatic cleanup of temporary files +- Optimized image quality settings (150 DPI, 80% quality) + +## Image Processing Settings + +Default image conversion settings: +- Format: PNG +- Density: 150 DPI +- Width: 1200px +- Height: 1600px +- Quality: 80% +- Aspect Ratio: Preserved + + + + + diff --git a/Utilities/image_answering_utility/src/services/pdf-image-service/config/s3Config.js b/Utilities/image_answering_utility/src/services/pdf-image-service/config/s3Config.js new file mode 100644 index 00000000..8e12ea33 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-image-service/config/s3Config.js @@ -0,0 +1,60 @@ +const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3'); +const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); +const path = require('path'); +require('dotenv').config({ path: path.join(__dirname, '../../../../config/.env') }); + +const s3Client = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY + }, + maxAttempts: 3, + forcePathStyle: true +}); + +const s3Config = { + client: s3Client, + bucketName: process.env.S3_BUCKET_NAME, + folderPath: process.env.S3_FOLDER_PATH || '', + urlExpiry: parseInt(process.env.S3_URL_EXPIRY, 10) || 604800, + + // Normalize S3 key to use forward slashes and remove any double slashes + normalizeKey(key) { + return key.replace(/\\/g, '/').replace(/\/+/g, '/').replace(/^\//, ''); + }, + + async uploadToS3(key, data) { + try { + const s3Key = this.normalizeKey(key); + const putCommand = new PutObjectCommand({ + Bucket: this.bucketName, + Key: s3Key, + Body: typeof data === 'string' ? data : JSON.stringify(data), + ContentType: 'application/json' + }); + + await this.client.send(putCommand); + return await this.generatePresignedUrl(s3Key); + } catch (error) { + throw new Error(`Error uploading to S3: ${error.message}`); + } + }, + + async generatePresignedUrl(key) { + try { + const s3Key = this.normalizeKey(key); + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: s3Key + }); + return await getSignedUrl(this.client, command, { + expiresIn: this.urlExpiry + }); + } catch (error) { + throw new Error(`Error generating presigned URL: ${error.message}`); + } + } +}; + +module.exports = { s3Config }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-image-service/controllers/ImageController.js b/Utilities/image_answering_utility/src/services/pdf-image-service/controllers/ImageController.js new file mode 100644 index 00000000..28651b14 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-image-service/controllers/ImageController.js @@ -0,0 +1,35 @@ +const { PDFImageService } = require('../services/PDFImageService'); +const logger = require('../utils/logger'); + +class ImageController { + constructor() { + this.pdfImageService = new PDFImageService(); + } + + async convertToImages(pdfPath, filename, options = {}) { + const { includeBase64 = false, uniqueId = null } = options; + + try { + logger.info(`Starting PDF to image conversion for ${filename} from path: ${pdfPath}`); + const result = await this.pdfImageService.convertPDFToImages(pdfPath, filename, includeBase64, uniqueId); + logger.info(`Successfully converted ${filename} to images`); + return result; + } catch (error) { + logger.error(`Error converting PDF to images for ${filename}: ${error.message}`); + logger.error(`File path: ${pdfPath}`); + throw new Error(`Failed to convert PDF to images: ${error.message}`); + } + } + + async generateImageUrl(key) { + try { + return await this.pdfImageService.generatePresignedUrl(key); + } catch (error) { + logger.error(`Error generating image URL for ${key}: ${error.message}`); + throw error; + } + } + +} + +module.exports = ImageController; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-image-service/services/PDFImageService.js b/Utilities/image_answering_utility/src/services/pdf-image-service/services/PDFImageService.js new file mode 100644 index 00000000..22f0596c --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-image-service/services/PDFImageService.js @@ -0,0 +1,178 @@ +const fs = require('fs-extra'); +const path = require('path'); +const { fromPath } = require('pdf2pic'); +const { PutObjectCommand } = require('@aws-sdk/client-s3'); +const { execSync } = require('child_process'); +const logger = require('../utils/logger'); +const { s3Config } = require('../config/s3Config'); +const { StorageManager } = require('../utils/StorageManager'); + +// Add environment variable configuration with defaults +const pdfConfig = { + density: process.env.PDF_IMAGE_DENSITY ? parseInt(process.env.PDF_IMAGE_DENSITY) : 150, + format: process.env.PDF_IMAGE_FORMAT || 'png', + width: process.env.PDF_IMAGE_WIDTH ? parseInt(process.env.PDF_IMAGE_WIDTH) : 1200, + height: process.env.PDF_IMAGE_HEIGHT ? parseInt(process.env.PDF_IMAGE_HEIGHT) : 1600, + preserveAspectRatio: process.env.PDF_PRESERVE_ASPECT_RATIO === 'false' ? false : true, + quality: process.env.PDF_IMAGE_QUALITY ? parseInt(process.env.PDF_IMAGE_QUALITY) : 80 +}; + +class PDFImageService { + constructor() { + this.storageManager = new StorageManager(); + this.checkDependencies(); + // Initialize concurrency limit with dynamic import + this.initializeConcurrencyLimit(); + this.activeConversions = new Set(); + } + + async initializeConcurrencyLimit() { + const { default: pLimit } = await import('p-limit'); + this.concurrencyLimit = pLimit(3); // Process 5 files at a time + } + + checkDependencies() { + try { + execSync('gm version'); + } catch (error) { + logger.error('GraphicsMagick is not installed. Please install it first.'); + throw new Error('GraphicsMagick is required but not installed'); + } + } + + async convertPDFToImages(pdfPath, filename, includeBase64 = false, uniqueId = null) { + // Ensure concurrency limit is initialized + if (!this.concurrencyLimit) { + await this.initializeConcurrencyLimit(); + } + + // Verify file exists and is accessible + try { + await fs.access(pdfPath); + } catch (error) { + throw new Error(`PDF file not accessible at path ${pdfPath}: ${error.message}`); + } + + const sanitizedFilename = filename.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase(); + const processId = uniqueId || `f_${sanitizedFilename}_${Date.now()}`; + let processDir = null; + + // Add to active conversions + this.activeConversions.add(processId); + + try { + processDir = await this.storageManager.createTempDirectory(processId); + // Use concurrency limit for the conversion + return await this.concurrencyLimit(async () => { + logger.info(`[${processId}] Starting conversion of ${filename} from ${pdfPath}`); + const convert = fromPath(pdfPath, { + density: pdfConfig.density, + format: pdfConfig.format, + width: pdfConfig.width, + height: pdfConfig.height, + preserveAspectRatio: pdfConfig.preserveAspectRatio, + quality: pdfConfig.quality, + saveFilename: `page`, + savePath: processDir + }); + + const pages = await convert.bulk(-1); + logger.info(`[${processId}] Converted PDF to ${pages.length} images`); + + return await this.processPages(pages, processId, includeBase64); + }); + } catch (error) { + logger.error(`[${processId}] Error during PDF conversion: ${error.message}`); + throw error; + } finally { + try { + // Remove from active conversions + this.activeConversions.delete(processId); + + if (processDir) { + // Ensure cleanup is complete + await this.storageManager.cleanup(processDir); + + // Double-check if directory still exists + if (await fs.pathExists(processDir)) { + logger.error(`Failed to remove directory: ${processDir}`); + // Try one more time with force + await fs.rm(processDir, { recursive: true, force: true }); + } + } + } catch (cleanupError) { + logger.error(`[${processId}] Cleanup error: ${cleanupError.message}`); + // Don't throw cleanup errors, but log them + } + } + } + + async processPages(pages, processId, includeBase64) { + // Process pages in batches to avoid memory issues + const batchSize = 5; + const results = []; + + for (let i = 0; i < pages.length; i += batchSize) { + const batch = pages.slice(i, i + batchSize); + const batchPromises = batch.map(async (page, index) => { + const pageNumber = i + index + 1; + try { + const imgBuffer = await fs.readFile(page.path); + const result = { + page_number: pageNumber, + image_url: null, + base64_data: includeBase64 ? imgBuffer.toString('base64') : undefined + }; + + const s3Key = `${processId}/page_${pageNumber}.png`.replace(/\\/g, '/'); + await this.uploadToS3(s3Key, imgBuffer); + result.image_url = await this.generatePresignedUrl(s3Key); + + logger.info(`[${processId}] Processed page ${pageNumber}`); + return result; + } catch (error) { + logger.error(`[${processId}] Error processing page ${pageNumber}: ${error.message}`); + return { + page_number: pageNumber, + error: error.message + }; + } + }); + + const batchResults = await Promise.all(batchPromises); + results.push(...batchResults); + } + + return results; + } + + async uploadToS3(key, buffer) { + try { + const s3Key = path.posix.join(s3Config.folderPath, key); + const command = new PutObjectCommand({ + Bucket: s3Config.bucketName, + Key: s3Key, + Body: buffer, + ContentType: 'image/png' + }); + await s3Config.client.send(command); + return s3Key; + } catch (error) { + logger.error(`Error uploading to S3: ${error.message}`); + throw error; + } + } + + async generatePresignedUrl(key) { + try { + const s3Key = path.posix.join(s3Config.folderPath, key); + return await s3Config.generatePresignedUrl(s3Key); + } catch (error) { + logger.error(`Error generating presigned URL: ${error.message}`); + throw error; + } + } + +} + +module.exports = { PDFImageService }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-image-service/utils/StorageManager.js b/Utilities/image_answering_utility/src/services/pdf-image-service/utils/StorageManager.js new file mode 100644 index 00000000..a3c8b011 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-image-service/utils/StorageManager.js @@ -0,0 +1,67 @@ +const fs = require('fs-extra'); +const path = require('path'); +const os = require('os'); +const logger = require('./logger'); + +class StorageManager { + constructor() { + this.baseDir = this.initializeBaseDirectory(); + this.processingDir = path.join(this.baseDir, 'processing_pdf_images'); + this.initializeDirectories(); + } + + initializeBaseDirectory() { + const projectBaseDir = path.join(__dirname, '../../../../storage'); + try { + fs.mkdirSync(projectBaseDir, { recursive: true }); + return projectBaseDir; + } catch (error) { + logger.warn(`Failed to create project storage directory: ${error.message}`); + const tempBaseDir = path.join(os.tmpdir(), 'pdf-processor'); + fs.mkdirSync(tempBaseDir, { recursive: true }); + return tempBaseDir; + } + } + + initializeDirectories() { + try { + fs.mkdirSync(this.processingDir, { recursive: true }); + logger.info('Storage directories initialized successfully'); + } catch (error) { + logger.error(`Error initializing directories: ${error.message}`); + throw error; + } + } + + async createTempDirectory(processId) { + const tempDir = path.join(this.processingDir, processId); + try { + await fs.mkdir(tempDir, { recursive: true }); + logger.info(`Created temporary directory: ${tempDir}`); + return tempDir; + } catch (error) { + logger.error(`Error creating temp directory: ${error.message}`); + throw error; + } + } + + async cleanup(directory) { + try { + if (await fs.pathExists(directory)) { + await fs.remove(directory); + logger.info(`Cleaned up directory: ${directory}`); + + if (await fs.pathExists(directory)) { + logger.warn(`Directory still exists after cleanup: ${directory}`); + await fs.rm(directory, { recursive: true, force: true }); + } + } + } catch (error) { + logger.error(`Cleanup error for ${directory}: ${error.message}`); + throw error; + } + } + +} + +module.exports = { StorageManager }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-image-service/utils/logger.js b/Utilities/image_answering_utility/src/services/pdf-image-service/utils/logger.js new file mode 100644 index 00000000..1f59edd1 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-image-service/utils/logger.js @@ -0,0 +1,6 @@ +const { createLogger } = require('../../../utils/logger'); + +// Create logger instance for PDF image service +const logger = createLogger('pdf-image'); + +module.exports = logger; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/config/s3Config.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/config/s3Config.js new file mode 100644 index 00000000..a03b3607 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/config/s3Config.js @@ -0,0 +1,54 @@ +const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3'); +const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); +const path = require('path'); +require('dotenv').config({ path: path.join(__dirname, '../../../../config/.env') }); + +const s3Client = new S3Client({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY + }, + maxAttempts: 3, + forcePathStyle: true +}); + +const s3Config = { + client: s3Client, + bucketName: process.env.S3_BUCKET_NAME, + folderPath: process.env.S3_FOLDER_PATH || '', + urlExpiry: parseInt(process.env.S3_URL_EXPIRY, 10) || 604800, + + async uploadToS3(key, data) { + try { + const s3Key = path.join(this.folderPath, key).replace(/\\/g, '/'); + const putCommand = new PutObjectCommand({ + Bucket: this.bucketName, + Key: s3Key, + Body: typeof data === 'string' ? data : JSON.stringify(data), + ContentType: 'application/json' + }); + + await this.client.send(putCommand); + return await this.generatePresignedUrl(s3Key); + } catch (error) { + throw new Error(`Error uploading to S3: ${error.message}`); + } + }, + + async generatePresignedUrl(key) { + try { + const command = new GetObjectCommand({ + Bucket: this.bucketName, + Key: key + }); + return await getSignedUrl(this.client, command, { + expiresIn: this.urlExpiry + }); + } catch (error) { + throw new Error(`Error generating presigned URL: ${error.message}`); + } + } +}; + +module.exports = { s3Config }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/config/serviceConfig.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/config/serviceConfig.js new file mode 100644 index 00000000..eced6af4 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/config/serviceConfig.js @@ -0,0 +1,44 @@ +const path = require('path'); +require('dotenv').config({ path: path.join(__dirname, '../../../../config/.env') }); + +const config = { + // Service URLs + markdownServiceUrl: process.env.MARKDOWN_SERVICE_URL, + callbackServiceUrl: process.env.CALLBACK_SERVICE_URL, + + // Processing configuration + maxConcurrentFiles: parseInt(process.env.MAX_CONCURRENT_FILES, 10), + maxRetries: parseInt(process.env.MAX_RETRIES, 10), + retryDelay: parseInt(process.env.RETRY_DELAY, 10), + requestTimeout: parseInt(process.env.REQUEST_TIMEOUT, 10), + + // Callback configuration + callbackRetries: parseInt(process.env.CALLBACK_RETRIES, 10), + callbackRetryDelay: parseInt(process.env.CALLBACK_RETRY_DELAY, 10), + + // Output configuration + outputDir: path.join(__dirname, '../../../../output'), + + // Validate required configuration + validate() { + const required = [ + 'markdownServiceUrl', + 'callbackServiceUrl', + 'maxConcurrentFiles', + 'maxRetries', + 'retryDelay', + 'requestTimeout' + ]; + + for (const key of required) { + if (!this[key]) { + throw new Error(`Missing required configuration: ${key}`); + } + } + } +}; + +// Validate configuration on load +config.validate(); + +module.exports = config; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/controllers/ProcessingController.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/controllers/ProcessingController.js new file mode 100644 index 00000000..4d286c59 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/controllers/ProcessingController.js @@ -0,0 +1,48 @@ +const { PDFProcessingService } = require('../services/PDFProcessingService'); +const logger = require('../utils/logger'); + +class ProcessingController { + constructor() { + this.processingService = new PDFProcessingService(); + } + + async processSinglePDF(filePath, options = {}) { + const { include_base64 = false, uniqueId = null } = options; + const filename = require('path').basename(filePath); + + try { + logger.info(`Starting to process PDF: ${filename} (ID: ${uniqueId || 'N/A'})`); + const result = await this.processingService.processPDF(filePath, filename, include_base64, uniqueId); + logger.info(`Completed processing PDF: ${filename} (ID: ${uniqueId || 'N/A'})`); + return result; + } catch (error) { + logger.error(`Error processing PDF ${filename}: ${error.message}`); + throw error; + } + } + + async processDirectory(inputPath, options = {}) { + const { include_base64 = false, uniqueId = null } = options; + + try { + logger.info(`Starting directory processing: ${inputPath} (ID: ${uniqueId || 'N/A'})`); + const results = await this.processingService.processAllPDFs(inputPath, include_base64, uniqueId); + logger.info(`Completed directory processing: ${inputPath} (ID: ${uniqueId || 'N/A'})`); + return results; + } catch (error) { + logger.error(`Error processing directory ${inputPath} (ID: ${uniqueId || 'N/A'}): ${error.message}`); + throw error; + } + } + + async getProcessingStatus(uniqueId) { + try { + return await this.processingService.getStatus(uniqueId); + } catch (error) { + logger.error(`Error getting status for ${uniqueId}: ${error.message}`); + throw error; + } + } +} + +module.exports = ProcessingController; diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/models/FileRecord.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/models/FileRecord.js new file mode 100644 index 00000000..3f59856b --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/models/FileRecord.js @@ -0,0 +1,42 @@ +const mongoose = require('mongoose'); + +const fileRecordSchema = new mongoose.Schema({ + uniqueId: { + type: String, + required: true, + unique: true, + index: true + }, + filename: { + type: String, + required: true + }, + filePath: String, + originalPath: String, + status: { + type: String, + enum: ['processing', 'completed', 'failed'], + default: 'processing' + }, + startTime: { + type: Date, + default: Date.now + }, + completedTime: Date, + lastUpdated: { + type: Date, + default: Date.now + }, + error: String, + s3Url: String +}); + +// Update lastUpdated timestamp before saving +fileRecordSchema.pre('save', function(next) { + this.lastUpdated = new Date(); + next(); +}); + +const FileRecord = mongoose.model('FileRecord', fileRecordSchema); + +module.exports = { FileRecord }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/models/FileStatus.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/models/FileStatus.js new file mode 100644 index 00000000..bbc2e68f --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/models/FileStatus.js @@ -0,0 +1,38 @@ +const mongoose = require('mongoose'); + +const fileStatusSchema = new mongoose.Schema({ + uniqueId: { + type: String, + required: true, + unique: true, + index: true + }, + filename: { + type: String, + required: true + }, + status: { + type: String, + enum: ['processing', 'completed', 'failed'], + default: 'processing' + }, + s3Url: String, + error: String, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}); + +fileStatusSchema.pre('save', function(next) { + this.updatedAt = new Date(); + next(); +}); + +const FileStatus = mongoose.model('FileStatus', fileStatusSchema); + +module.exports = { FileStatus }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/services/CallbackService.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/services/CallbackService.js new file mode 100644 index 00000000..a6543ff8 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/services/CallbackService.js @@ -0,0 +1,67 @@ +const axios = require('axios'); +const logger = require('../utils/logger'); +const config = require('../config/serviceConfig'); +const { FileStatus } = require('../models/FileStatus'); + +class CallbackService { + constructor() { + this.client = axios.create({ + headers: { + 'Content-Type': 'application/json' + } + }); + } + + async sendCallback(filename, s3Url, success = true, error = null, uniqueId) { + const status = success ? "completed" : "failed"; + const callbackData = { + filename, + uniqueId, + status, + timestamp: new Date().toISOString(), + s3_url: s3Url, + error: error + }; + + // Update MongoDB status + try { + await FileStatus.findOneAndUpdate( + { uniqueId }, + { + status, + s3Url, + error, + filename + }, + { upsert: true } + ); + logger.info(`MongoDB status updated for ${filename} (ID: ${uniqueId})`); + } catch (dbError) { + logger.error(`Failed to update MongoDB status for ${filename} (ID: ${uniqueId}): ${dbError.message}`); + } + + // Send callback to external service + for (let attempt = 1; attempt <= config.callbackRetries; attempt++) { + try { + const response = await this.client.post( + config.callbackServiceUrl, + callbackData + ); + + if (response.status === 200) { + logger.info(`Callback sent successfully for ${filename} (ID: ${uniqueId})`); + return; + } + } catch (error) { + logger.warn(`Callback attempt ${attempt} failed for ${filename} (ID: ${uniqueId}): ${error.message}`); + if (attempt < config.callbackRetries) { + await new Promise(resolve => setTimeout(resolve, config.callbackRetryDelay)); + } + } + } + + logger.error(`All callback attempts failed for ${filename} (ID: ${uniqueId})`); + } +} + +module.exports = { CallbackService }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/services/MarkdownService.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/services/MarkdownService.js new file mode 100644 index 00000000..234b3f45 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/services/MarkdownService.js @@ -0,0 +1,97 @@ +const FormData = require('form-data'); +const { createReadStream } = require('fs'); +const axios = require('axios'); +const logger = require('../utils/logger'); +const config = require('../config/serviceConfig'); +const { withRetry } = require('../utils/retryUtils'); + +class MarkdownService { + constructor() { + this.client = axios.create({ + timeout: config.requestTimeout, + maxBodyLength: Infinity, + maxContentLength: Infinity + }); + } + + async processFile(filePath, uniqueId) { + let fileStream = null; + const filename = require('path').basename(filePath); + + try { + // Wrap the entire processing logic in retry mechanism + return await withRetry( + async () => { + // Create a new form and stream for each attempt + if (fileStream) { + fileStream.destroy(); + } + + fileStream = createReadStream(filePath); + const formData = new FormData(); + + // Handle stream errors + fileStream.on('error', (error) => { + logger.error(`[${uniqueId}] Stream error for ${filename}: ${error.message}`); + fileStream.destroy(); + throw error; + }); + + formData.append('file', fileStream, { + filename: filename, + contentType: 'application/pdf' + }); + + logger.info(`[${uniqueId}] Sending PDF to markdown service: ${filename}`); + + const response = await this.client.post(config.markdownServiceUrl, formData, { + headers: { + ...formData.getHeaders(), + 'Accept': 'application/json', + 'Connection': 'keep-alive' + }, + validateStatus: status => status >= 200 && status < 500, + timeout: config.requestTimeout || 300000, // 5 minutes default + }); + + if (response.status !== 200) { + throw new Error(`Markdown service error (${response.status}): ${JSON.stringify(response.data)}`); + } + + logger.info(`[${uniqueId}] Successfully processed markdown for: ${filename}`); + return response.data; + }, + { + maxRetries: 3, + initialDelay: 2000, // Start with 2 seconds delay + maxDelay: 10000, // Max 10 seconds delay + shouldRetry: (error) => { + // Retry on connection errors and 5xx server errors + const isConnectionError = error.code === 'ECONNREFUSED' || + error.code === 'ECONNRESET' || + error.code === 'ETIMEDOUT'; + const isServerError = error.response?.status >= 500; + const isRetryable = isConnectionError || isServerError; + + if (isRetryable) { + logger.info(`[${uniqueId}] Error is retryable: ${error.message}`); + } + return isRetryable; + } + } + ); + } catch (error) { + logger.error(`[${uniqueId}] Error in markdown processing for ${filename}: ${error.message}`); + if (error.response?.data) { + logger.error(`[${uniqueId}] Response data: ${JSON.stringify(error.response.data)}`); + } + throw new Error(`Markdown processing failed: ${error.message}`); + } finally { + if (fileStream) { + fileStream.destroy(); + } + } + } +} + +module.exports = { MarkdownService }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/services/PDFProcessingService.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/services/PDFProcessingService.js new file mode 100644 index 00000000..69561bda --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/services/PDFProcessingService.js @@ -0,0 +1,213 @@ +const fs = require('fs-extra'); +const path = require('path'); +const FormData = require('form-data'); +const { createReadStream } = require('fs'); +const logger = require('../utils/logger'); +const { MarkdownService } = require('./MarkdownService'); +const { ResultsManager } = require('../utils/ResultsManager'); +const { FileTracker } = require('../utils/FileTracker'); +const { StorageManager } = require('../utils/StorageManager'); +const ImageController = require('../../pdf-image-service/controllers/ImageController'); +const { FileStatus } = require('../models/FileStatus'); + +class PDFProcessingService { + constructor() { + this.markdownService = new MarkdownService(); + this.resultsManager = new ResultsManager(); + this.fileTracker = new FileTracker(); + this.storageManager = new StorageManager(); + this.imageController = new ImageController(); + } + + async processPDF(filePath, filename, include_base64 = false, uniqueId = null) { + const startTime = Date.now(); + + // Use provided uniqueId or generate new one + const fileUniqueId = uniqueId || await this.fileTracker.generateUniqueId(filename); + + if (uniqueId) { + logger.info(`Using provided uniqueId: ${fileUniqueId}`); + } else { + logger.info(`Generated new uniqueId: ${fileUniqueId}`); + } + + try { + // Verify file exists and is accessible + try { + await fs.access(filePath); + } catch (error) { + throw new Error(`PDF file not accessible: ${error.message}`); + } + + // Track file processing + await this.fileTracker.trackFile(filename, filePath, fileUniqueId); + logger.info(`Starting to process: ${filename} (ID: ${fileUniqueId})`); + + const fileStats = await fs.stat(filePath); + logger.info(`File size: ${(fileStats.size / (1024 * 1024)).toFixed(2)} MB`); + + // Process PDF with proper error handling + let markdownResult = null; + let imageResults = null; + + try { + [markdownResult, imageResults] = await Promise.all([ + this.processMarkdown(filePath, fileUniqueId).catch(error => { + logger.error(`Markdown processing failed for ${filename}: ${error.message}`); + return null; + }), + this.processImages(filePath, include_base64, fileUniqueId).catch(error => { + logger.error(`Image processing failed for ${filename}: ${error.message}`); + return null; + }) + ]); + + if (!markdownResult && !imageResults) { + throw new Error('Both markdown and image processing failed'); + } + } catch (error) { + logger.error(`Processing failed for ${filename}: ${error.message}`); + throw error; + } + + // Save and combine results + const results = await this.resultsManager.saveResults( + filename, + markdownResult, + imageResults, + fileUniqueId + ); + + const processingTime = (Date.now() - startTime) / 1000; + await this.fileTracker.updateStatus(fileUniqueId, 'completed', { + s3Url: results.s3_url + }); + + + return { + filename, + uniqueId: fileUniqueId, + success: true, + processingTime, + s3_url: results.s3_url, + markdown_status: markdownResult ? 'success' : 'failed', + image_status: imageResults ? 'success' : 'failed' + }; + + } catch (error) { + const processingTime = (Date.now() - startTime) / 1000; + await this.fileTracker.updateStatus(fileUniqueId, 'failed', { + error: error.message + }); + + return { + filename, + uniqueId: fileUniqueId, + success: false, + error: error.message, + processingTime + }; + } + } + + async processMarkdown(filePath, uniqueId) { + try { + return await this.markdownService.processFile(filePath, uniqueId); + } catch (error) { + if (error.message.includes('service is currently unavailable')) { + throw error; // Propagate service unavailability errors + } + logger.error(`Error in markdown processing: ${error.message}`); + throw new Error(`Markdown processing failed: ${error.message}`); + } + } + + async processImages(filePath, include_base64, uniqueId) { + try { + return await this.imageController.convertToImages(filePath, path.basename(filePath), { + includeBase64: include_base64, + uniqueId + }); + } catch (error) { + logger.error(`Error in image processing: ${error.message}`); + throw new Error(`Image processing failed: ${error.message}`); + } + } + + async processAllPDFs(inputFolderPath, include_base64 = false, uniqueId = null) { + const processId = uniqueId || `batch_${Date.now()}`; + + if (uniqueId) { + logger.info(`Using provided batch uniqueId: ${processId}`); + } else { + logger.info(`Generated new batch uniqueId: ${processId}`); + } + + const tempDir = await this.storageManager.createTempDirectory(processId); + + try { + // Copy files to temp directory + const files = await this.storageManager.getAllPDFFiles(inputFolderPath); + const tempFiles = await Promise.all(files.map(async (file) => { + const tempFile = path.join(tempDir, path.basename(file)); + await fs.copyFile(file, tempFile); + return tempFile; + })); + + // Get concurrency limit from env or default to 3 + const BATCH_SIZE = parseInt(process.env.MAX_CONCURRENT_PROCESSING, 10) || 3; + logger.info(`Processing PDFs with batch size: ${BATCH_SIZE}`); + + // Process files in concurrent batches + const results = []; + for (let i = 0; i < tempFiles.length; i += BATCH_SIZE) { + const batch = tempFiles.slice(i, i + BATCH_SIZE); + logger.info(`Processing batch ${Math.floor(i/BATCH_SIZE) + 1} of ${Math.ceil(tempFiles.length/BATCH_SIZE)}`); + + const batchResults = await Promise.all(batch.map(async (filePath) => { + try { + const filename = path.basename(filePath); + // Create a unique ID for each file that includes the batch ID + const fileUniqueId = uniqueId ? + `${uniqueId}_${filename.replace(/\.[^/.]+$/, '')}` : + await this.fileTracker.generateUniqueId(filename); + + logger.info(`Starting to process ${filename} with ID: ${fileUniqueId}`); + // Log explicitly that we're using a derived uniqueId + if (uniqueId) { + logger.info(`Using derived uniqueId from batch: ${fileUniqueId}`); + } + const result = await this.processPDF(filePath, filename, include_base64, fileUniqueId); + logger.info(`Completed processing ${filename}`); + return result; + } catch (error) { + const filename = path.basename(filePath); + logger.error(`Error processing ${filename}: ${error.message}`); + const errorUniqueId = uniqueId ? + `${uniqueId}_${filename.replace(/\.[^/.]+$/, '')}` : + await this.fileTracker.generateUniqueId(filename); + return { + filename, + success: false, + error: error.message, + uniqueId: errorUniqueId + }; + } + })); + + results.push(...batchResults); + logger.info(`Completed batch ${Math.floor(i/BATCH_SIZE) + 1}`); + } + + return results; + } finally { + await this.storageManager.cleanup(tempDir); + } + } + + async getStatus(uniqueId) { + return this.fileTracker.getStatus(uniqueId); + } +} + +module.exports = { PDFProcessingService }; diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/FileTracker.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/FileTracker.js new file mode 100644 index 00000000..8e9c3531 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/FileTracker.js @@ -0,0 +1,122 @@ +const path = require('path'); +const crypto = require('crypto'); +const logger = require('./logger'); +const { FileRecord } = require('../models/FileRecord'); +const { MongoManager } = require('./MongoManager'); +const mongoose = require('mongoose'); + +class FileTracker { + constructor() { + // Ensure MongoDB connection + MongoManager.reconnectIfNeeded(); + } + + async generateUniqueId(filename) { + const timestamp = Date.now(); + return `${filename.replace(/[^a-zA-Z0-9]/g, '_')}_${timestamp}`; + } + + async trackFile(filename, filePath, uniqueId) { + try { + await MongoManager.reconnectIfNeeded(); + + // First check if record already exists with processing status + const existingRecord = await FileRecord.findOne({ uniqueId }); + + if (existingRecord && existingRecord.status === 'processing') { + // Record already exists with processing status, just update lastUpdated + logger.info(`File ${filename} (ID: ${uniqueId}) already in processing state, continuing processing`); + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + lastUpdated: new Date() + }, + { new: true } + ); + return existingRecord; + } + + // Otherwise create or update record with processing status + const record = await FileRecord.findOneAndUpdate( + { uniqueId }, + { + filename, + filePath, + status: 'processing', + startTime: new Date(), + lastUpdated: new Date() + }, + { + upsert: true, + new: true, + setDefaultsOnInsert: true + } + ); + + logger.info(`Tracking file: ${filename} (ID: ${uniqueId})`); + return record; + } catch (error) { + logger.error(`Error tracking file ${filename}: ${error.message}`); + throw error; + } + } + + async updateStatus(uniqueId, status, additionalData = {}) { + try { + await MongoManager.reconnectIfNeeded(); + + const updateData = { + status, + lastUpdated: new Date(), + ...additionalData + }; + + if (status === 'completed') { + updateData.completedTime = new Date(); + } + + const record = await FileRecord.findOneAndUpdate( + { uniqueId }, + updateData, + { new: true } + ); + + if (!record) { + throw new Error(`No record found for ID: ${uniqueId}`); + } + + logger.info(`Updated status for ${uniqueId} to ${status}`); + return record; + } catch (error) { + logger.error(`Error updating status for ${uniqueId}: ${error.message}`); + throw error; + } + } + + async getStatus(uniqueId) { + try { + await MongoManager.reconnectIfNeeded(); + return await FileRecord.findOne({ uniqueId }); + } catch (error) { + logger.error(`Error getting status for ${uniqueId}: ${error.message}`); + throw error; + } + } + + async cleanupOldRecords(maxAge = 7 * 24 * 60 * 60 * 1000) { // 7 days + try { + await MongoManager.reconnectIfNeeded(); + + const cutoffDate = new Date(Date.now() - maxAge); + const result = await FileRecord.deleteMany({ + lastUpdated: { $lt: cutoffDate } + }); + + logger.info(`Cleaned up ${result.deletedCount} old records`); + } catch (error) { + logger.error(`Error cleaning up records: ${error.message}`); + } + } +} + +module.exports = { FileTracker }; diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/MongoManager.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/MongoManager.js new file mode 100644 index 00000000..fcbd5933 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/MongoManager.js @@ -0,0 +1,40 @@ +const mongoose = require('mongoose'); +const logger = require('./logger'); +const config = require('../../../config/mongodb-config'); + +class MongoManager { + static async connect() { + try { + await mongoose.connect(config.uri, { + serverSelectionTimeoutMS: 5000, // 5 second timeout + socketTimeoutMS: 45000, // 45 second timeout + family: 4 // Use IPv4, skip trying IPv6 + }); + logger.info('Successfully connected to MongoDB'); + } catch (error) { + logger.error(`MongoDB connection error: ${error.message}`); + // Don't throw error - allow app to function without DB + } + } + + static async disconnect() { + try { + await mongoose.disconnect(); + logger.info('Disconnected from MongoDB'); + } catch (error) { + logger.error(`MongoDB disconnect error: ${error.message}`); + } + } + + static async isConnected() { + return mongoose.connection.readyState === 1; + } + + static async reconnectIfNeeded() { + if (mongoose.connection.readyState !== 1) { + await this.connect(); + } + } +} + +module.exports = { MongoManager }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/ResultsManager.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/ResultsManager.js new file mode 100644 index 00000000..4849b8c4 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/ResultsManager.js @@ -0,0 +1,129 @@ +const logger = require('./logger'); +const { s3Config } = require('../config/s3Config'); +const { CallbackService } = require('../services/CallbackService'); +const { FileRecord } = require('../models/FileRecord'); + +class ResultsManager { + constructor() { + this.callbackService = new CallbackService(); + } + + async saveResults(filename, markdownResult, imageResults, uniqueId) { + try { + // Combine results + const combinedResult = await this.combineResults(filename, markdownResult, imageResults, uniqueId); + + // Update database directly instead of using callback + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + status: 'completed', + s3Url: combinedResult.s3_url, + completedTime: new Date(), + lastUpdated: new Date() + }, + { new: true } + ); + + // Comment out callback service + // await this.callbackService.sendCallback(filename, combinedResult.s3_url, true, null, uniqueId); + + logger.info(`Results saved for ${filename} (ID: ${uniqueId})`); + return combinedResult; + } catch (error) { + logger.error(`Error in saveResults for ${filename} (ID: ${uniqueId}): ${error.message}`); + + // Update database with error status + await FileRecord.findOneAndUpdate( + { uniqueId }, + { + status: 'failed', + error: error.message, + lastUpdated: new Date() + }, + { new: true } + ); + + // Comment out callback service + // await this.callbackService.sendCallback( + // filename, + // null, + // false, + // error.message, + // uniqueId + // ); + throw error; + } + } + + async combineResults(filename, markdownResult, imageResults, uniqueId) { + try { + // Extract relevant data from both results + const combinedData = { + status: "success", + filename: filename, + uniqueId: uniqueId, + timestamp: new Date().toISOString(), + pages: [] + }; + + // Ensure both results exist and have the expected structure + if (!markdownResult || !markdownResult.chunks) { + throw new Error('Invalid markdown result structure'); + } + + if (!Array.isArray(imageResults)) { + throw new Error('Invalid image result structure'); + } + + // Sort image results by page number + const sortedImageResults = imageResults.sort((a, b) => a.page_number - b.page_number); + + // Combine page data + for (let i = 0; i < sortedImageResults.length; i++) { + const imagePage = sortedImageResults[i]; + const pageNumber = imagePage.page_number; + const markdownPage = markdownResult.chunks.find( + chunk => chunk.page_number === pageNumber + ); + + combinedData.pages.push({ + page_number: pageNumber, + image_url: imagePage.image_url, + base64_data: imagePage.base64_data, + markdown_text: markdownPage ? markdownPage.chunkText : '', + // markdown_metadata: markdownPage ? markdownPage.metadata : {} + }); + } + + // Add total pages and file info + combinedData.total_pages = sortedImageResults.length; + combinedData.has_base64 = sortedImageResults.some(page => !!page.base64_data); + + // Upload to S3 + const s3Key = `final_results/${uniqueId}/${filename.replace('.pdf', '')}_combined.json`; + const s3Url = await this.uploadToS3(s3Key, combinedData); + + // Add S3 URL to the response + combinedData.s3_url = s3Url; + + logger.info(`Combined result uploaded to S3: ${s3Url}`); + return combinedData; + + } catch (error) { + logger.error(`Error combining results for ${filename}: ${error.message}`); + throw error; + } + } + + async uploadToS3(key, data) { + try { + return await s3Config.uploadToS3(key, data); + } catch (error) { + logger.error(`Error uploading to S3: ${error.message}`); + throw error; + } + } +} + +module.exports = { ResultsManager }; diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/StorageManager.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/StorageManager.js new file mode 100644 index 00000000..93232731 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/StorageManager.js @@ -0,0 +1,112 @@ +const fs = require('fs-extra'); +const path = require('path'); +const os = require('os'); +const logger = require('./logger'); + +class StorageManager { + constructor() { + this.baseDir = this.initializeBaseDirectory(); + this.processingDir = path.join(this.baseDir, 'processing_pdf'); + this.initializeDirectories(); + } + + initializeBaseDirectory() { + const projectBaseDir = path.join(__dirname, '../../../../storage'); + try { + fs.mkdirSync(projectBaseDir, { recursive: true }); + return projectBaseDir; + } catch (error) { + logger.warn(`Failed to create project storage directory: ${error.message}`); + const tempBaseDir = path.join(os.tmpdir(), 'pdf-processor'); + fs.mkdirSync(tempBaseDir, { recursive: true }); + return tempBaseDir; + } + } + + initializeDirectories() { + try { + fs.mkdirSync(this.processingDir, { recursive: true }); + logger.info('Storage directories initialized successfully'); + } catch (error) { + logger.error(`Error initializing directories: ${error.message}`); + throw error; + } + } + + async createTempDirectory(processId) { + const tempDir = path.join(this.processingDir, processId); + try { + await fs.mkdir(tempDir, { recursive: true }); + logger.info(`Created temporary directory: ${tempDir}`); + return tempDir; + } catch (error) { + logger.error(`Error creating temp directory: ${error.message}`); + throw error; + } + } + + async cleanup(directory) { + try { + if (await fs.pathExists(directory)) { + await fs.remove(directory); + logger.info(`Cleaned up directory: ${directory}`); + } + } catch (error) { + logger.warn(`Cleanup warning for ${directory}: ${error.message}`); + } + } + + async cleanupOldDirectories() { + const MAX_AGE = 2 * 60 * 60 * 1000; // 2 hours + const now = Date.now(); + + try { + const contents = await fs.readdir(this.processingDir); + for (const item of contents) { + const fullPath = path.join(this.processingDir, item); + try { + const stats = await fs.stat(fullPath); + if (now - stats.mtimeMs > MAX_AGE) { + await fs.remove(fullPath); + logger.info(`Cleaned up old directory: ${fullPath}`); + } + } catch (err) { + logger.warn(`Error checking directory ${fullPath}: ${err.message}`); + } + } + } catch (err) { + logger.error(`Error during cleanup: ${err.message}`); + throw err; + } + } + + async getAllPDFFiles(dirPath) { + let results = []; + + try { + const items = await fs.readdir(dirPath); + + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stat = await fs.stat(fullPath); + + if (stat.isDirectory()) { + // Recursively get files from subdirectory + logger.info(`Scanning directory: ${fullPath}`); + const subDirFiles = await this.getAllPDFFiles(fullPath); + results = results.concat(subDirFiles); + } else if (item.toLowerCase().endsWith('.pdf')) { + // Add PDF file to results + results.push(fullPath); + } + } + } catch (error) { + logger.error(`Error scanning directory ${dirPath}: ${error.message}`); + throw error; + } + + return results; + } +} + +module.exports = { StorageManager }; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/logger.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/logger.js new file mode 100644 index 00000000..ec68c3a4 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/logger.js @@ -0,0 +1,6 @@ +const { createLogger } = require('../../../utils/logger'); + +// Create logger instance for PDF processing service +const logger = createLogger('pdf-processing'); + +module.exports = logger; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/retryUtils.js b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/retryUtils.js new file mode 100644 index 00000000..ade1e356 --- /dev/null +++ b/Utilities/image_answering_utility/src/services/pdf-processing-service/utils/retryUtils.js @@ -0,0 +1,46 @@ +const logger = require('./logger'); + +/** + * Implements exponential backoff retry logic + * @param {Function} operation - Function to retry + * @param {Object} options - Retry options + * @param {number} options.maxRetries - Maximum number of retry attempts + * @param {number} options.initialDelay - Initial delay in milliseconds + * @param {number} options.maxDelay - Maximum delay between retries + * @param {Function} options.shouldRetry - Function to determine if error is retryable + * @returns {Promise} - Resolves with operation result or rejects with error + */ +async function withRetry(operation, { + maxRetries = 3, + initialDelay = 1000, + maxDelay = 10000, + shouldRetry = () => true +} = {}) { + let lastError; + let delay = initialDelay; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await operation(); + } catch (error) { + lastError = error; + + if (!shouldRetry(error) || attempt === maxRetries) { + throw error; + } + + logger.warn(`Operation failed, attempt ${attempt}/${maxRetries}. Error: ${error.message}`); + logger.info(`Retrying in ${delay}ms...`); + + await new Promise(resolve => setTimeout(resolve, delay)); + // Exponential backoff with max delay cap + delay = Math.min(delay * 2, maxDelay); + } + } + + throw lastError; +} + +module.exports = { + withRetry +}; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/utils/ensure-logs-dir.js b/Utilities/image_answering_utility/src/utils/ensure-logs-dir.js new file mode 100644 index 00000000..3baadede --- /dev/null +++ b/Utilities/image_answering_utility/src/utils/ensure-logs-dir.js @@ -0,0 +1,19 @@ +const fs = require('fs-extra'); +const path = require('path'); +require('dotenv').config(); + +function ensureLogsDirectory() { + const logsDir = path.join(__dirname, '../../logs'); + try { + fs.ensureDirSync(logsDir); + process.env.LOGS_DIR = logsDir; + } catch (error) { + console.error(`Error creating logs directory: ${error.message}`); + // Fallback to OS temp directory if project directory is not accessible + const tempLogsDir = path.join(require('os').tmpdir(), 'pdf-processor-logs'); + fs.ensureDirSync(tempLogsDir); + process.env.LOGS_DIR = tempLogsDir; + } +} + +module.exports = ensureLogsDirectory; \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/utils/file-tracker.js b/Utilities/image_answering_utility/src/utils/file-tracker.js new file mode 100644 index 00000000..21940708 --- /dev/null +++ b/Utilities/image_answering_utility/src/utils/file-tracker.js @@ -0,0 +1,81 @@ +const fs = require('fs').promises; +const path = require('path'); +require('dotenv').config(); + +class FileTracker { + constructor() { + this.records = new Map(); + this.logFile = path.join(process.env.LOGS_DIR, 'file_processing_records.json'); + } + + generateUniqueId(filename) { + return `${filename.replace('.pdf', '')}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + async trackFile(filename, originalPath, existingUniqueId = null) { + const uniqueId = existingUniqueId || this.generateUniqueId(filename); + const record = { + uniqueId, + filename, + originalPath, + startTime: new Date().toISOString(), + status: 'processing', + completedTime: null, + error: null, + s3Url: null + }; + + this.records.set(uniqueId, record); + await this.saveRecords(); + return uniqueId; + } + + async updateStatus(uniqueId, status, details = {}) { + if (!this.records.has(uniqueId)) { + throw new Error(`No record found for ID: ${uniqueId}`); + } + + const record = this.records.get(uniqueId); + record.status = status; + record.completedTime = new Date().toISOString(); + + if (details.error) { + record.error = details.error; + } + if (details.s3Url) { + record.s3Url = details.s3Url; + } + + await this.saveRecords(); + return record; + } + + getRecord(uniqueId) { + return this.records.get(uniqueId); + } + + async saveRecords() { + const recordsArray = Array.from(this.records.values()); + await fs.writeFile( + this.logFile, + JSON.stringify(recordsArray, null, 2), + 'utf8' + ); + } + + async loadRecords() { + try { + const data = await fs.readFile(this.logFile, 'utf8'); + const records = JSON.parse(data); + this.records = new Map( + records.map(record => [record.uniqueId, record]) + ); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + } +} + +module.exports = new FileTracker(); \ No newline at end of file diff --git a/Utilities/image_answering_utility/src/utils/logger.js b/Utilities/image_answering_utility/src/utils/logger.js new file mode 100644 index 00000000..85aa9803 --- /dev/null +++ b/Utilities/image_answering_utility/src/utils/logger.js @@ -0,0 +1,55 @@ +const winston = require('winston'); +const path = require('path'); +const fs = require('fs-extra'); +require('dotenv').config({ path: path.join(__dirname, '../../config/.env') }); + +// Define log directory +const LOG_DIR = path.join(__dirname, '../../logs'); + +// Ensure logs directory exists +fs.ensureDirSync(LOG_DIR); + +// Define log format +const logFormat = winston.format.combine( + winston.format.timestamp(), + winston.format.printf(({ timestamp, level, message, service }) => { + return `${timestamp} [${service}] [${level.toUpperCase()}]: ${message}`; + }) +); + +/** + * Creates a logger instance for a specific service + * @param {string} serviceName - Name of the service (e.g., 'pdf-processing', 'pdf-image') + * @returns {winston.Logger} - Configured logger instance + */ +function createLogger(serviceName) { + return winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: logFormat, + defaultMeta: { service: serviceName }, + transports: [ + // Error logs + new winston.transports.File({ + filename: path.join(LOG_DIR, `${serviceName}-error.log`), + level: 'error' + }), + // Combined logs + new winston.transports.File({ + filename: path.join(LOG_DIR, `${serviceName}.log`) + }), + // Console output + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + logFormat + ) + }) + ] + }); +} + +// Export the logger factory +module.exports = { + createLogger, + LOG_DIR +}; \ No newline at end of file