From 9efaa55235b85100eebf354fe052f7b455232d10 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 22 Jan 2025 15:52:41 -0800 Subject: [PATCH 01/33] IO-3092 basic URL signing and image/pdf/video thumb generation. --- package-lock.json | 560 ++++++++++++++++++++++++++++++++++ package.json | 1 + server/media/imgprox-media.js | 136 +++++++++ server/routes/mediaRoutes.js | 7 + 4 files changed, 704 insertions(+) create mode 100644 server/media/imgprox-media.js diff --git a/package-lock.json b/package-lock.json index 4705bef33..0ae94d2a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@aws-sdk/client-secrets-manager": "^3.693.0", "@aws-sdk/client-ses": "^3.693.0", "@aws-sdk/credential-provider-node": "^3.693.0", + "@aws-sdk/s3-request-presigner": "^3.731.1", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -1101,6 +1102,499 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.731.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.731.1.tgz", + "integrity": "sha512-GdG0pXkcTgBpenouB834FoCHyLaivV2rGQn7OEQBiT8SBaTxSackZ6tGlJQAlzZQkiQfE/NePUJU7DczJZZvrg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.731.0", + "@aws-sdk/types": "3.731.0", + "@aws-sdk/util-format-url": "3.731.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/core": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.731.0.tgz", + "integrity": "sha512-ithBN1VWASkvAIlozJmenqDvNnFddr/SZXAs58+jCnBHgy3tXLHABZGVNCjetZkHRqNdXEO1kirnoxaFeXMeDA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.731.0", + "@smithy/core": "^3.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.731.0.tgz", + "integrity": "sha512-J9aKyQaVoec5eWTSDfO4h2sKHNP0wTzN15LFcHnkD+e/d0rdmOi7BTkkbJrIaynma9WShIasmrtM3HNi9GiiTA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.731.0", + "@aws-sdk/types": "3.731.0", + "@aws-sdk/util-arn-parser": "3.723.0", + "@smithy/core": "^3.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-stream": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.731.0.tgz", + "integrity": "sha512-1r/b4Os15dR+BCVRRLVQJMF7Krq6xX6IKHxN43kuvODYWz8Nv3XDlaSpeRpAzyJuzW/fTp4JgE+z0+gmJfdEeA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.731.0", + "@aws-sdk/types": "3.731.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/types": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", + "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/core": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.1.tgz", + "integrity": "sha512-hhUZlBWYuh9t6ycAcN90XOyG76C1AzwxZZgaCVPMYpWqqk9uMFo7HGG5Zu2cEhCJn7DdOi5krBmlibWWWPgdsw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.1", + "@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.0.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/middleware-endpoint": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.2.tgz", + "integrity": "sha512-Z9m67CXizGpj8CF/AW/7uHqYNh1VXXOn9Ap54fenWsCa0HnT4cJuE61zqG3cBkTZJDCy0wHJphilI41co/PE5g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.1.1", + "@smithy/middleware-serde": "^4.0.1", + "@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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/middleware-serde": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.1.tgz", + "integrity": "sha512-Fh0E2SOF+S+P1+CsgKyiBInAt3o2b6Qk7YOp2W0Qx2XnfTdfMuSDKUEcnrtpxCzgKJnqXeLUZYqtThaP0VGqtA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/smithy-client": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.2.tgz", + "integrity": "sha512-0yApeHWBqocelHGK22UivZyShNxFbDNrgREBllGh5Ws0D0rg/yId/CJfeoKKpjbfY2ju8j6WgDUGZHYQmINZ5w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.1.1", + "@smithy/middleware-endpoint": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", + "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/util-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.0.2.tgz", + "integrity": "sha512-0eZ4G5fRzIoewtHtwaYyl8g2C+osYOT4KClXgfdNEDAgkbe2TYPqcnw4GAWabqkZCax2ihRGPe9LZnsPdIUIHA==", + "license": "Apache-2.0", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/signature-v4-multi-region": { "version": "3.696.0", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.696.0.tgz", @@ -1177,6 +1671,72 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.731.0.tgz", + "integrity": "sha512-wZHObjnYmiz8wFlUQ4/5dHsT7k0at+GvZM02LgvshcRJLnFjYdrzjelMKuNynd/NNK3gLgTsFTGuIgPpz9r4rA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.731.0", + "@smithy/querystring-builder": "^4.0.0", + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url/node_modules/@aws-sdk/types": { + "version": "3.731.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", + "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", + "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url/node_modules/@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==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.693.0.tgz", diff --git a/package.json b/package.json index cd9ec1870..45028a531 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@aws-sdk/client-secrets-manager": "^3.693.0", "@aws-sdk/client-ses": "^3.693.0", "@aws-sdk/credential-provider-node": "^3.693.0", + "@aws-sdk/s3-request-presigner": "^3.731.1", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", diff --git a/server/media/imgprox-media.js b/server/media/imgprox-media.js new file mode 100644 index 000000000..6bb8e7a19 --- /dev/null +++ b/server/media/imgprox-media.js @@ -0,0 +1,136 @@ +const path = require("path"); +require("dotenv").config({ + path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) +}); +const logger = require("../utils/logger"); +const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3"); +const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); +const crypto = require("crypto"); +const { InstanceRegion } = require("../utils/instanceMgr"); + +//TODO: Remove hardcoded values. +const imgproxyBaseUrl = process.env.IMGPROXY_BASE_URL || `https://d3ictiiutovkvi.cloudfront.net`; +const imgproxyKey = process.env.IMGPROXY_KEY || `secret`; +const imgproxySalt = process.env.IMGPROXY_SALT || `salt`; +const imgproxyDestinationBucket = process.env.IMGPROXY_DESTINATION_BUCKET || `imex-shop-media`; + +//Generate a signed upload link for the S3 bucket. +//All uploads must be going to the same shop and jobid. +exports.generateSignedUploadUrls = async (req, res) => { + const { filenames, bodyshopid, jobid } = req.body; + try { + logger.log("imgproxy-upload-start", "DEBUG", req.user?.email, jobid, { filenames, bodyshopid, jobid }); + + //TODO: Ensure that the user has access to the given bodyshopid. + //This can be done by querying associations, or, maintaining a REDIS cache of user permissions. + const hasAccess = true; //TODO: Ensure this is not hardcoded. + if (!hasAccess) { + res.send(403); + return; + } + + const signedUrls = []; + for (const filename of filenames) { + // TODO: Implement a different, unique file naming convention. + const key = GenerateKey({ bodyshopid, jobid, filename }); + const client = new S3Client({ region: InstanceRegion() }); + const command = new PutObjectCommand({ Bucket: imgproxyDestinationBucket, Key: key }); + const presignedUrl = await getSignedUrl(client, command, { expiresIn: 360 }); + signedUrls.push({ filename, presignedUrl }); + } + + logger.log("imgproxy-upload-success", "DEBUG", req.user?.email, jobid, { signedUrls }); + res.json({ + success: true, + signedUrls + }); + } catch (error) { + res.status(400).json({ + success: false, + message: error.message, + stack: error.stack + }); + logger.log("imgproxy-upload-error", "ERROR", req.user?.email, jobid, { + message: error.message, + stack: error.stack + }); + } +}; + +exports.getThumbnailUrls = async (req, res) => { + const { jobid } = req.body; + try { + //TODO: Query for all documents related to the job. + //Delayed as the key structure may change slightly from what it is currently and will require evaluating mobile components. + // const { data } = await client.query({ + // query: queries.GET_DOCUMENTS_BY_JOBID, + // variables: { jobid } + // }); + + //Mocked Keys. + const keys = [ + "shopid/jobid/test2.jpg-1737502469411", + "shopid/jobid/test2.jpg-1737502469411", + "shopid/jobid/movie.mov-1737504997897", + "shopid/jobid/pdf.pdf-1737504944260" + ]; + + const thumbResizeParams = `rs:fill:250:250:1/g:ce`; + const proxiedUrls = keys.map((key) => { + //Format to follow: + /////< base 64 URL encoded to image path> + + // Build the S3 path to the object. + const fullS3Path = `s3://${imgproxyDestinationBucket}/${key}`; + const base64UrlEncodedKeyString = base64UrlEncode(fullS3Path); + //Thumbnail Generation Block + const thumbProxyPath = `${thumbResizeParams}/${base64UrlEncodedKeyString}`; + const thumbHmacSalt = createHmacSha256(`${imgproxySalt}/${thumbProxyPath}`); + + //Full Size URL block + + const fullSizeProxyPath = `${base64UrlEncodedKeyString}`; + const fullSizeHmacSalt = createHmacSha256(`${imgproxySalt}/${fullSizeProxyPath}`); + + //If not a picture, we need to get a signed download link to the file using S3 (or cloudfront preferably) + + return { + originalUrl: `${imgproxyBaseUrl}/${fullSizeHmacSalt}/${fullSizeProxyPath}`, + thumbnailUrl: `${imgproxyBaseUrl}/${thumbHmacSalt}/${thumbProxyPath}` + }; + }); + + res.json({ proxiedUrls }); + //Iterate over them, build the link based on the media type, and return the array. + } catch (error) { + logger.log("imgproxy-get-proxied-urls-error", "ERROR", req.user?.email, jobid, { + message: error.message, + stack: error.stack + }); + res.status(400).json({ message: error.message, stack: error.stack }); + } +}; + +exports.getBillFiles = async (req, res) => { + //Givena bill ID, get the documents associated to it. +}; + +exports.downloadFiles = async (req, res) => { + //Given a series of document IDs or keys, generate a file (or a link) to download all images in bulk +}; + +exports.deleteFiles = async (req, res) => { + //Mark a file for deletion in s3. Lifecycle deletion will actually delete the copy in the future. + //Mark as deleted from the documents section of the database. +}; + +function GenerateKey({ bodyshopid, jobid, filename }) { + return `${bodyshopid}/${jobid}/${filename}-${Date.now()}`; +} + +function base64UrlEncode(str) { + return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); +} +function createHmacSha256(data) { + return crypto.createHmac("sha256", imgproxyKey).update(data).digest("base64url"); +} diff --git a/server/routes/mediaRoutes.js b/server/routes/mediaRoutes.js index 699579bb9..c93dbed8a 100644 --- a/server/routes/mediaRoutes.js +++ b/server/routes/mediaRoutes.js @@ -1,6 +1,10 @@ const express = require("express"); const router = express.Router(); const { createSignedUploadURL, downloadFiles, renameKeys, deleteFiles } = require("../media/media"); +const { + generateSignedUploadUrls, + getThumbnailUrls +} = require("../media/imgprox-media"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); router.use(validateFirebaseIdTokenMiddleware); @@ -10,4 +14,7 @@ router.post("/download", downloadFiles); router.post("/rename", renameKeys); router.post("/delete", deleteFiles); +router.post("/proxy/sign", generateSignedUploadUrls); +router.post("/proxy/get", getThumbnailUrls); + module.exports = router; From 47fe1959b1b0773ffb38acd1bb2b1e4c5a3abeb7 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 5 Feb 2025 11:03:29 -0800 Subject: [PATCH 02/33] IO-3092 WIP on img proxy thumbnail generation. --- .../documents-upload-imgproxy.component.jsx | 123 ++++++++++++ .../documents-upload-imgproxy.utility.js | 181 ++++++++++++++++++ .../jobs-documents-gallery.component.jsx | 112 ++++++++++- server/graphql-client/queries.js | 38 +++- server/media/imgprox-media.js | 67 ++++--- server/routes/mediaRoutes.js | 9 +- 6 files changed, 489 insertions(+), 41 deletions(-) create mode 100644 client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.component.jsx create mode 100644 client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js diff --git a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.component.jsx b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.component.jsx new file mode 100644 index 000000000..259ca5831 --- /dev/null +++ b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.component.jsx @@ -0,0 +1,123 @@ +import { UploadOutlined } from "@ant-design/icons"; +import { Progress, Result, Space, Upload } from "antd"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import formatBytes from "../../utils/formatbytes"; +import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; +import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component"; +import { handleUpload } from "./documents-upload-imgproxy.utility.js"; + +const mapStateToProps = createStructuredSelector({ + currentUser: selectCurrentUser, + bodyshop: selectBodyshop +}); + +export function DocumentsUploadImgproxyComponent({ + children, + currentUser, + bodyshop, + jobId, + tagsArray, + billId, + callbackAfterUpload, + totalSize, + ignoreSizeLimit = false +}) { + const { t } = useTranslation(); + const [fileList, setFileList] = useState([]); + const notification = useNotification(); + + const pct = useMemo(() => { + return parseInt((totalSize / ((bodyshop && bodyshop.jobsizelimit) || 1)) * 100); + }, [bodyshop, totalSize]); + + if (pct > 100 && !ignoreSizeLimit) + return ( + + ); + + const handleDone = (uid) => { + setTimeout(() => { + setFileList((fileList) => fileList.filter((x) => x.uid !== uid)); + }, 2000); + }; + const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); + + return ( + { + if (f.event && f.event.percent === 100) handleDone(f.file.uid); + setFileList(f.fileList); + }} + beforeUpload={(file, fileList) => { + if (ignoreSizeLimit) return true; + const newFiles = fileList.reduce((acc, val) => acc + val.size, 0); + const shouldStopUpload = (totalSize + newFiles) / ((bodyshop && bodyshop.jobsizelimit) || 1) >= 1; + + //Check to see if old files plus newly uploaded ones will be too much. + if (shouldStopUpload) { + notification.open({ + key: "cannotuploaddocuments", + type: "error", + message: t("documents.labels.upload_limitexceeded_title"), + description: t("documents.labels.upload_limitexceeded") + }); + return Upload.LIST_IGNORE; + } + return true; + }} + customRequest={(ev) => + handleUpload( + ev, + { + bodyshop: bodyshop, + uploaded_by: currentUser.email, + jobId: jobId, + billId: billId, + tagsArray: tagsArray, + callback: callbackAfterUpload + }, + notification + ) + } + accept="audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx" + // showUploadList={false} + > + {children || ( + <> +

+ +

+

+ {t("documents.labels.dragtoupload")} +

+ {!ignoreSizeLimit && ( + + + + {t("documents.labels.usage", { + percent: pct, + used: formatBytes(totalSize), + total: formatBytes(bodyshop && bodyshop.jobsizelimit) + })} + + + )} + + )} +
+ ); +} + +export default connect(mapStateToProps, null)(DocumentsUploadImgproxyComponent); diff --git a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js new file mode 100644 index 000000000..f9dd2a193 --- /dev/null +++ b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js @@ -0,0 +1,181 @@ +import axios from "axios"; +import exifr from "exifr"; +import i18n from "i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; +import { axiosAuthInterceptorId } from "../../utils/CleanAxios"; +import client from "../../utils/GraphQLClient"; + +//Context: currentUserEmail, bodyshop, jobid, invoiceid + +//Required to prevent headers from getting set and rejected from Cloudinary. +var cleanAxios = axios.create(); +cleanAxios.interceptors.request.eject(axiosAuthInterceptorId); + +export const handleUpload = (ev, context, notification) => { + logImEXEvent("document_upload", { filetype: ev.file.type }); + + const { onError, onSuccess, onProgress } = ev; + const { bodyshop, jobId } = context; + + const fileName = ev.file.name || ev.filename; + + let key = replaceAccents(fileName).replace(/[^A-Z0-9.]+/gi, "_"); + let extension = fileName.split(".").pop(); + uploadToS3(key, extension, ev.file.type, ev.file, onError, onSuccess, onProgress, context, notification); +}; + +//Handles only 1 file at a time. +export const uploadToS3 = async ( + key, + extension, + fileType, + file, + onError, + onSuccess, + onProgress, + context, + notification +) => { + const { bodyshop, jobId, billId, uploaded_by, callback, tagsArray } = context; + + //Set variables for getting the signed URL. + let timestamp = Math.floor(Date.now() / 1000); + //Get the signed url. + + const signedURLResponse = await axios.post("/media/proxy/sign", { + filenames: [key], + bodyshopid: bodyshop.id, + jobid: jobId + }); + + if (signedURLResponse.status !== 200) { + if (onError) onError(signedURLResponse.statusText); + notification["error"]({ + message: i18n.t("documents.errors.getpresignurl", { + message: signedURLResponse.statusText + }) + }); + return; + } + + const { presignedUrl: preSignedUploadUrlToS3, key: s3Key } = signedURLResponse.data.signedUrls[0]; + + //Build request to end to cloudinary. + var options = { + // headers: { "X-Requested-With": "XMLHttpRequest" }, + onUploadProgress: (e) => { + if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 }); + } + }; + + // const formData = new FormData(); + // formData.append("file", file); + + // formData.append("upload_preset", upload_preset); + + // formData.append("api_key", import.meta.env.VITE_APP_CLOUDINARY_API_KEY); + // formData.append("public_id", public_id); + // formData.append("tags", tags); + // formData.append("timestamp", timestamp); + // formData.append("signature", signature); + + const cloudinaryUploadResponse = await cleanAxios.put(preSignedUploadUrlToS3, file, options); + + //Insert the document with the matching key. + let takenat; + if (fileType.includes("image")) { + try { + const exif = await exifr.parse(file); + takenat = exif && exif.DateTimeOriginal; + } catch (error) { + console.log("Unable to parse image file for EXIF Data"); + } + } + + const documentInsert = await client.mutate({ + mutation: INSERT_NEW_DOCUMENT, + variables: { + docInput: [ + { + ...(jobId ? { jobid: jobId } : {}), + ...(billId ? { billid: billId } : {}), + uploaded_by: uploaded_by, + key: s3Key, + type: fileType, + extension: cloudinaryUploadResponse.data.format || extension, + bodyshopid: bodyshop.id, + size: cloudinaryUploadResponse.data.bytes || file.size, + takenat + } + ] + } + }); + if (!documentInsert.errors) { + if (onSuccess) + onSuccess({ + //TODO: Since this may go server side, we can just manage the state locally. + uid: documentInsert.data.insert_documents.returning[0].id, + name: documentInsert.data.insert_documents.returning[0].name, + status: "done", + key: documentInsert.data.insert_documents.returning[0].key + }); + notification.open({ + type: "success", + key: "docuploadsuccess", + message: i18n.t("documents.successes.insert") + }); + if (callback) { + callback(); + } + } else { + if (onError) onError(JSON.stringify(documentInsert.errors)); + notification["error"]({ + message: i18n.t("documents.errors.insert", { + message: JSON.stringify(documentInsert.errors) + }) + }); + return; + } +}; + +//Also needs to be updated in media JS and mobile app. +export function DetermineFileType(filetype) { + if (!filetype) return "auto"; + else if (filetype.startsWith("image")) return "image"; + else if (filetype.startsWith("video")) return "video"; + else if (filetype.startsWith("application/pdf")) return "image"; + else if (filetype.startsWith("application")) return "raw"; + + return "auto"; +} + +function replaceAccents(str) { + // Verifies if the String has accents and replace them + if (str.search(/[\xC0-\xFF]/g) > -1) { + str = str + .replace(/[\xC0-\xC5]/g, "A") + .replace(/[\xC6]/g, "AE") + .replace(/[\xC7]/g, "C") + .replace(/[\xC8-\xCB]/g, "E") + .replace(/[\xCC-\xCF]/g, "I") + .replace(/[\xD0]/g, "D") + .replace(/[\xD1]/g, "N") + .replace(/[\xD2-\xD6\xD8]/g, "O") + .replace(/[\xD9-\xDC]/g, "U") + .replace(/[\xDD]/g, "Y") + .replace(/[\xDE]/g, "P") + .replace(/[\xE0-\xE5]/g, "a") + .replace(/[\xE6]/g, "ae") + .replace(/[\xE7]/g, "c") + .replace(/[\xE8-\xEB]/g, "e") + .replace(/[\xEC-\xEF]/g, "i") + .replace(/[\xF1]/g, "n") + .replace(/[\xF2-\xF6\xF8]/g, "o") + .replace(/[\xF9-\xFC]/g, "u") + .replace(/[\xFE]/g, "p") + .replace(/[\xFD\xFF]/g, "y"); + } + + return str; +} diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx index 0dcc18855..27af3defe 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx @@ -1,24 +1,25 @@ import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons"; import { Button, Card, Col, Row, Space } from "antd"; +import axios from "axios"; import React, { useEffect, useState } from "react"; import { Gallery } from "react-grid-gallery"; import { useTranslation } from "react-i18next"; +import Lightbox from "react-image-lightbox"; +import "react-image-lightbox/style.css"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import DocumentsUploadImgproxyComponent from "../documents-upload-imgproxy/documents-upload-imgproxy.component"; import DocumentsUploadComponent from "../documents-upload/documents-upload.component"; import { DetermineFileType } from "../documents-upload/documents-upload.utility"; +import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; +import UpsellComponent, { upsellEnum } from "../upsell/upsell.component"; import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility"; import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.component"; import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.component"; import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component"; import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-gallery.selectall.component"; -import Lightbox from "react-image-lightbox"; -import "react-image-lightbox/style.css"; -import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; - -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import UpsellComponent, { upsellEnum } from "../upsell/upsell.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); @@ -114,6 +115,89 @@ function JobsDocumentsComponent({ ); setgalleryImages(documents); }, [data, setgalleryImages, t]); + + const getProxiedUrls = async () => { + const result = await axios.post("/media/proxy/thumbnails", { jobid: jobId }); + + result.data.forEach((r) => console.log(r.thumbnailUrl)); + + let documents = result.data.reduce( + (acc, value) => { + const fileType = DetermineFileType(value.type); + if (value.type.startsWith("image")) { + acc.images.push({ + src: value.thumbnailUrl, + fullsize: value.originalUrl, + height: 225, + width: 225, + isSelected: false, + key: value.key, + extension: value.extension, + id: value.id, + type: value.type, + size: value.size, + tags: [{ value: value.type, title: value.type }] + }); + } else { + let thumb; + switch (fileType) { + case "raw": + thumb = `${window.location.origin}/file.png`; + break; + default: + thumb = GenerateThumbUrl(value); + break; + } + + const fileName = value.key.split("/").pop(); + acc.other.push({ + source: value.originalUrlViaProxyPath, + src: value.thumbnailUrl, + fullsize: value.presignedGetUrl, + tags: [ + { + value: fileName, + title: fileName + }, + + { value: value.type, title: value.type }, + ...(value.bill + ? [ + { + value: value.bill.vendor.name, + title: t("vendors.fields.name") + }, + { value: value.bill.date, title: t("bills.fields.date") }, + { + value: value.bill.invoice_number, + title: t("bills.fields.invoice_number") + } + ] + : []) + ], + height: 225, + width: 225, + isSelected: false, + extension: value.extension, + key: value.key, + id: value.id, + type: value.type, + size: value.size + }); + } + + return acc; + }, + { images: [], other: [] } + ); + console.log("*** ~ file: jobs-documents-gallery.component.jsx:198 ~ getProxiedUrls ~ documents:", documents); + setgalleryImages(documents); + }; + + // useEffect(() => { + // getProxiedUrls(); + // }, [galleryImages]); + const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); return ( @@ -138,6 +222,18 @@ function JobsDocumentsComponent({ )} + + + + + + { const client = new S3Client({ region: InstanceRegion() }); const command = new PutObjectCommand({ Bucket: imgproxyDestinationBucket, Key: key }); const presignedUrl = await getSignedUrl(client, command, { expiresIn: 360 }); - signedUrls.push({ filename, presignedUrl }); + signedUrls.push({ filename, presignedUrl, key }); } logger.log("imgproxy-upload-success", "DEBUG", req.user?.email, jobid, { signedUrls }); @@ -58,30 +61,25 @@ exports.generateSignedUploadUrls = async (req, res) => { }; exports.getThumbnailUrls = async (req, res) => { - const { jobid } = req.body; + const { jobid, billid } = req.body; + try { //TODO: Query for all documents related to the job. //Delayed as the key structure may change slightly from what it is currently and will require evaluating mobile components. - // const { data } = await client.query({ - // query: queries.GET_DOCUMENTS_BY_JOBID, - // variables: { jobid } - // }); - //Mocked Keys. - const keys = [ - "shopid/jobid/test2.jpg-1737502469411", - "shopid/jobid/test2.jpg-1737502469411", - "shopid/jobid/movie.mov-1737504997897", - "shopid/jobid/pdf.pdf-1737504944260" - ]; + const client = req.userGraphQLClient; + const data = await client.request(GET_DOCUMENTS_BY_JOB, { jobId: jobid }); const thumbResizeParams = `rs:fill:250:250:1/g:ce`; - const proxiedUrls = keys.map((key) => { + const s3client = new S3Client({ region: InstanceRegion() }); + const proxiedUrls = []; + + for (const document of data.documents) { //Format to follow: /////< base 64 URL encoded to image path> // Build the S3 path to the object. - const fullS3Path = `s3://${imgproxyDestinationBucket}/${key}`; + const fullS3Path = `s3://${imgproxyDestinationBucket}/${document.key}`; const base64UrlEncodedKeyString = base64UrlEncode(fullS3Path); //Thumbnail Generation Block const thumbProxyPath = `${thumbResizeParams}/${base64UrlEncodedKeyString}`; @@ -92,15 +90,30 @@ exports.getThumbnailUrls = async (req, res) => { const fullSizeProxyPath = `${base64UrlEncodedKeyString}`; const fullSizeHmacSalt = createHmacSha256(`${imgproxySalt}/${fullSizeProxyPath}`); - //If not a picture, we need to get a signed download link to the file using S3 (or cloudfront preferably) + const s3Props = {}; + if (!document.type.startsWith("image")) { + //If not a picture, we need to get a signed download link to the file using S3 (or cloudfront preferably) + const command = new GetObjectCommand({ Bucket: imgproxyDestinationBucket, Key: document.key }); + const presignedGetUrl = await getSignedUrl(s3client, command, { expiresIn: 360 }); + s3Props.presignedGetUrl = presignedGetUrl; - return { + const originalProxyPath = `raw:1/${base64UrlEncodedKeyString}`; + const originalHmacSalt = createHmacSha256(`${imgproxySalt}/${originalProxyPath}`); + s3Props.originalUrlViaProxyPath = `${imgproxyBaseUrl}/${originalHmacSalt}/${originalProxyPath}`; + } + + proxiedUrls.push({ originalUrl: `${imgproxyBaseUrl}/${fullSizeHmacSalt}/${fullSizeProxyPath}`, - thumbnailUrl: `${imgproxyBaseUrl}/${thumbHmacSalt}/${thumbProxyPath}` - }; - }); + thumbnailUrl: `${imgproxyBaseUrl}/${thumbHmacSalt}/${thumbProxyPath}`, + fullS3Path, + base64UrlEncodedKeyString, + thumbProxyPath, + ...s3Props, + ...document + }); + } - res.json({ proxiedUrls }); + res.json(proxiedUrls); //Iterate over them, build the link based on the media type, and return the array. } catch (error) { logger.log("imgproxy-get-proxied-urls-error", "ERROR", req.user?.email, jobid, { @@ -124,8 +137,12 @@ exports.deleteFiles = async (req, res) => { //Mark as deleted from the documents section of the database. }; +//Gerneate a key for the s3 bucket by popping off the extension, add a timestamp, and add back the extension. +//This is to prevent any collisions/duplicates in the bucket. function GenerateKey({ bodyshopid, jobid, filename }) { - return `${bodyshopid}/${jobid}/${filename}-${Date.now()}`; + let nameArray = filename.split("."); + let extension = nameArray.pop(); + return `${bodyshopid}/${jobid}/${nameArray.join(".")}-${Date.now()}.${extension}`; } function base64UrlEncode(str) { diff --git a/server/routes/mediaRoutes.js b/server/routes/mediaRoutes.js index c93dbed8a..9718b85d1 100644 --- a/server/routes/mediaRoutes.js +++ b/server/routes/mediaRoutes.js @@ -1,13 +1,12 @@ const express = require("express"); const router = express.Router(); const { createSignedUploadURL, downloadFiles, renameKeys, deleteFiles } = require("../media/media"); -const { - generateSignedUploadUrls, - getThumbnailUrls -} = require("../media/imgprox-media"); +const { generateSignedUploadUrls, getThumbnailUrls } = require("../media/imgprox-media"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); router.use(validateFirebaseIdTokenMiddleware); +router.use(withUserGraphQLClientMiddleware); router.post("/sign", createSignedUploadURL); router.post("/download", downloadFiles); @@ -15,6 +14,6 @@ router.post("/rename", renameKeys); router.post("/delete", deleteFiles); router.post("/proxy/sign", generateSignedUploadUrls); -router.post("/proxy/get", getThumbnailUrls); +router.post("/proxy/thumbnails", getThumbnailUrls); module.exports = router; From fbb473941cfefd3d71c005be844c8f1de0863de6 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 6 Feb 2025 14:37:16 -0800 Subject: [PATCH 03/33] IO-3092 implement backwards compatibility for Cloudinary documents. --- .../documents-upload-imgproxy.utility.js | 13 ++------ .../jobs-documents-gallery.component.jsx | 7 ++--- server.js | 2 +- server/media/imgprox-media.js | 31 ++++++++++++------- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js index f9dd2a193..280469669 100644 --- a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js +++ b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js @@ -20,8 +20,9 @@ export const handleUpload = (ev, context, notification) => { const fileName = ev.file.name || ev.filename; - let key = replaceAccents(fileName).replace(/[^A-Z0-9.]+/gi, "_"); let extension = fileName.split(".").pop(); + let key = `${bodyshop.id}/${jobId}/${replaceAccents(fileName).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}.${extension}`; + uploadToS3(key, extension, ev.file.type, ev.file, onError, onSuccess, onProgress, context, notification); }; @@ -69,16 +70,6 @@ export const uploadToS3 = async ( } }; - // const formData = new FormData(); - // formData.append("file", file); - - // formData.append("upload_preset", upload_preset); - - // formData.append("api_key", import.meta.env.VITE_APP_CLOUDINARY_API_KEY); - // formData.append("public_id", public_id); - // formData.append("tags", tags); - // formData.append("timestamp", timestamp); - // formData.append("signature", signature); const cloudinaryUploadResponse = await cleanAxios.put(preSignedUploadUrlToS3, file, options); diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx index 27af3defe..a3d667a35 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx @@ -118,9 +118,6 @@ function JobsDocumentsComponent({ const getProxiedUrls = async () => { const result = await axios.post("/media/proxy/thumbnails", { jobid: jobId }); - - result.data.forEach((r) => console.log(r.thumbnailUrl)); - let documents = result.data.reduce( (acc, value) => { const fileType = DetermineFileType(value.type); @@ -195,8 +192,8 @@ function JobsDocumentsComponent({ }; // useEffect(() => { - // getProxiedUrls(); - // }, [galleryImages]); + // if (data) getProxiedUrls(); + // }, [data]); const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); diff --git a/server.js b/server.js index ef480a7ea..b70b4e7cd 100644 --- a/server.js +++ b/server.js @@ -293,7 +293,7 @@ const applySocketIO = async ({ server, app }) => { */ const main = async () => { const app = express(); - const port = process.env.PORT || 5000; + const port = process.env.PORT || 4000; const server = http.createServer(app); diff --git a/server/media/imgprox-media.js b/server/media/imgprox-media.js index 065de42da..2ee4d5d5b 100644 --- a/server/media/imgprox-media.js +++ b/server/media/imgprox-media.js @@ -9,13 +9,10 @@ const crypto = require("crypto"); const { InstanceRegion } = require("../utils/instanceMgr"); const { GET_DOCUMENTS_BY_JOB } = require("../graphql-client/queries"); //TODO: Remove hardcoded values. -const imgproxyBaseUrl = - process.env.IMGPROXY_BASE_URL || - // `https://k2car6fha7w5cbgry3j2td56ra0kdmwn.lambda-url.ca-central-1.on.aws` || - `https://d3ictiiutovkvi.cloudfront.net`; -const imgproxyKey = process.env.IMGPROXY_KEY || `secret`; -const imgproxySalt = process.env.IMGPROXY_SALT || `salt`; -const imgproxyDestinationBucket = process.env.IMGPROXY_DESTINATION_BUCKET || `imex-shop-media`; +const imgproxyBaseUrl = process.env.IMGPROXY_BASE_URL; +const imgproxyKey = process.env.IMGPROXY_KEY; +const imgproxySalt = process.env.IMGPROXY_SALT; +const imgproxyDestinationBucket = process.env.IMGPROXY_DESTINATION_BUCKET; //Generate a signed upload link for the S3 bucket. //All uploads must be going to the same shop and jobid. @@ -35,7 +32,7 @@ exports.generateSignedUploadUrls = async (req, res) => { const signedUrls = []; for (const filename of filenames) { // TODO: Implement a different, unique file naming convention. - const key = GenerateKey({ bodyshopid, jobid, filename }); + const key = filename; //GenerateKey({ bodyshopid, jobid, filename }); const client = new S3Client({ region: InstanceRegion() }); const command = new PutObjectCommand({ Bucket: imgproxyDestinationBucket, Key: key }); const presignedUrl = await getSignedUrl(client, command, { expiresIn: 360 }); @@ -76,10 +73,17 @@ exports.getThumbnailUrls = async (req, res) => { for (const document of data.documents) { //Format to follow: - /////< base 64 URL encoded to image path> + /////< base 64 URL encoded to image path> + //When working with documents from Cloudinary, the URL does not include the extension. + let key; + if (/\.[^/.]+$/.test(document.key)) { + key = document.key; + } else { + key = `${document.key}.${document.extension.toLowerCase()}`; + } // Build the S3 path to the object. - const fullS3Path = `s3://${imgproxyDestinationBucket}/${document.key}`; + const fullS3Path = `s3://${imgproxyDestinationBucket}/${key}`; const base64UrlEncodedKeyString = base64UrlEncode(fullS3Path); //Thumbnail Generation Block const thumbProxyPath = `${thumbResizeParams}/${base64UrlEncodedKeyString}`; @@ -93,7 +97,10 @@ exports.getThumbnailUrls = async (req, res) => { const s3Props = {}; if (!document.type.startsWith("image")) { //If not a picture, we need to get a signed download link to the file using S3 (or cloudfront preferably) - const command = new GetObjectCommand({ Bucket: imgproxyDestinationBucket, Key: document.key }); + const command = new GetObjectCommand({ + Bucket: imgproxyDestinationBucket, + Key: key + }); const presignedGetUrl = await getSignedUrl(s3client, command, { expiresIn: 360 }); s3Props.presignedGetUrl = presignedGetUrl; @@ -142,7 +149,7 @@ exports.deleteFiles = async (req, res) => { function GenerateKey({ bodyshopid, jobid, filename }) { let nameArray = filename.split("."); let extension = nameArray.pop(); - return `${bodyshopid}/${jobid}/${nameArray.join(".")}-${Date.now()}.${extension}`; + return `${bodyshopid}/${jobid}/${nameArray.join(".")}-${Date.now()}`; } function base64UrlEncode(str) { From b069b6bc4cb5652064f69d8d1f7f3380f48c316e Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 7 Feb 2025 13:33:22 -0800 Subject: [PATCH 04/33] IO-3092 Implement delete, move and download on image proxy. Add imgproxy based components. --- .../documents-upload-imgproxy.utility.js | 34 +- .../jobs-documents-gallery.component.jsx | 96 +--- .../jobs-documents-gallery.container.jsx | 52 +- ...nt-imgproxy-gallery.download.component.jsx | 78 +++ ...nt-imgproxy-gallery.reassign.component.jsx | 129 +++++ ...s-documents-imgproxy-gallery.component.jsx | 247 +++++++++ ...s-documents-imgproxy-gallery.container.jsx | 29 + ...ents-imgproxy-gallery.delete.component.jsx | 68 +++ ...ts-imgproxy-gallery.external.component.jsx | 50 ++ ...s-imgproxy-gallery.selectall.component.jsx | 55 ++ ...obs-documents-imgproxy-gallery.styles.scss | 10 + .../temporary-docs.component.jsx | 19 +- package-lock.json | 511 +++++++++++++++++- package.json | 2 + server/graphql-client/queries.js | 27 + server/media/imgprox-media.js | 233 +++++++- server/routes/mediaRoutes.js | 15 +- 17 files changed, 1491 insertions(+), 164 deletions(-) create mode 100644 client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx create mode 100644 client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx create mode 100644 client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx create mode 100644 client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.container.jsx create mode 100644 client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx create mode 100644 client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx create mode 100644 client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.selectall.component.jsx create mode 100644 client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.styles.scss diff --git a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js index 280469669..184cf86c6 100644 --- a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js +++ b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js @@ -38,13 +38,10 @@ export const uploadToS3 = async ( context, notification ) => { - const { bodyshop, jobId, billId, uploaded_by, callback, tagsArray } = context; + const { bodyshop, jobId, billId, uploaded_by, callback } = context; - //Set variables for getting the signed URL. - let timestamp = Math.floor(Date.now() / 1000); - //Get the signed url. - - const signedURLResponse = await axios.post("/media/proxy/sign", { + //Get the signed url allowing us to PUT to S3. + const signedURLResponse = await axios.post("/media/imgproxy/sign", { filenames: [key], bodyshopid: bodyshop.id, jobid: jobId @@ -60,19 +57,16 @@ export const uploadToS3 = async ( return; } + //Key should be same as we provided to maintain backwards compatibility. const { presignedUrl: preSignedUploadUrlToS3, key: s3Key } = signedURLResponse.data.signedUrls[0]; - //Build request to end to cloudinary. var options = { - // headers: { "X-Requested-With": "XMLHttpRequest" }, onUploadProgress: (e) => { if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 }); } }; - - const cloudinaryUploadResponse = await cleanAxios.put(preSignedUploadUrlToS3, file, options); - + const s3UploadResponse = await cleanAxios.put(preSignedUploadUrlToS3, file, options); //Insert the document with the matching key. let takenat; if (fileType.includes("image")) { @@ -94,18 +88,18 @@ export const uploadToS3 = async ( uploaded_by: uploaded_by, key: s3Key, type: fileType, - extension: cloudinaryUploadResponse.data.format || extension, + extension: s3UploadResponse.data.format || extension, bodyshopid: bodyshop.id, - size: cloudinaryUploadResponse.data.bytes || file.size, + size: s3UploadResponse.data.bytes || file.size, //Leftover from Cloudinary. We don't do any optimization on upload, so it will always be file.size. takenat } ] } }); + if (!documentInsert.errors) { if (onSuccess) onSuccess({ - //TODO: Since this may go server side, we can just manage the state locally. uid: documentInsert.data.insert_documents.returning[0].id, name: documentInsert.data.insert_documents.returning[0].name, status: "done", @@ -130,17 +124,6 @@ export const uploadToS3 = async ( } }; -//Also needs to be updated in media JS and mobile app. -export function DetermineFileType(filetype) { - if (!filetype) return "auto"; - else if (filetype.startsWith("image")) return "image"; - else if (filetype.startsWith("video")) return "video"; - else if (filetype.startsWith("application/pdf")) return "image"; - else if (filetype.startsWith("application")) return "raw"; - - return "auto"; -} - function replaceAccents(str) { // Verifies if the String has accents and replace them if (str.search(/[\xC0-\xFF]/g) > -1) { @@ -167,6 +150,5 @@ function replaceAccents(str) { .replace(/[\xFE]/g, "p") .replace(/[\xFD\xFF]/g, "y"); } - return str; } diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx index a3d667a35..fdc919320 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx @@ -1,7 +1,6 @@ import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons"; import { Button, Card, Col, Row, Space } from "antd"; -import axios from "axios"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { Gallery } from "react-grid-gallery"; import { useTranslation } from "react-i18next"; import Lightbox from "react-image-lightbox"; @@ -9,7 +8,6 @@ import "react-image-lightbox/style.css"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import DocumentsUploadImgproxyComponent from "../documents-upload-imgproxy/documents-upload-imgproxy.component"; import DocumentsUploadComponent from "../documents-upload/documents-upload.component"; import { DetermineFileType } from "../documents-upload/documents-upload.utility"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; @@ -116,85 +114,6 @@ function JobsDocumentsComponent({ setgalleryImages(documents); }, [data, setgalleryImages, t]); - const getProxiedUrls = async () => { - const result = await axios.post("/media/proxy/thumbnails", { jobid: jobId }); - let documents = result.data.reduce( - (acc, value) => { - const fileType = DetermineFileType(value.type); - if (value.type.startsWith("image")) { - acc.images.push({ - src: value.thumbnailUrl, - fullsize: value.originalUrl, - height: 225, - width: 225, - isSelected: false, - key: value.key, - extension: value.extension, - id: value.id, - type: value.type, - size: value.size, - tags: [{ value: value.type, title: value.type }] - }); - } else { - let thumb; - switch (fileType) { - case "raw": - thumb = `${window.location.origin}/file.png`; - break; - default: - thumb = GenerateThumbUrl(value); - break; - } - - const fileName = value.key.split("/").pop(); - acc.other.push({ - source: value.originalUrlViaProxyPath, - src: value.thumbnailUrl, - fullsize: value.presignedGetUrl, - tags: [ - { - value: fileName, - title: fileName - }, - - { value: value.type, title: value.type }, - ...(value.bill - ? [ - { - value: value.bill.vendor.name, - title: t("vendors.fields.name") - }, - { value: value.bill.date, title: t("bills.fields.date") }, - { - value: value.bill.invoice_number, - title: t("bills.fields.invoice_number") - } - ] - : []) - ], - height: 225, - width: 225, - isSelected: false, - extension: value.extension, - key: value.key, - id: value.id, - type: value.type, - size: value.size - }); - } - - return acc; - }, - { images: [], other: [] } - ); - console.log("*** ~ file: jobs-documents-gallery.component.jsx:198 ~ getProxiedUrls ~ documents:", documents); - setgalleryImages(documents); - }; - - // useEffect(() => { - // if (data) getProxiedUrls(); - // }, [data]); - const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); return ( @@ -218,19 +137,6 @@ function JobsDocumentsComponent({ )} - - - - - - - ; if (error) return ; - return ( - - ); + if (useImgProxy) { + return ( + + ); + } else { + return ( + + ); + } } diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx new file mode 100644 index 000000000..2d649bcc9 --- /dev/null +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx @@ -0,0 +1,78 @@ +import { Button, Space } from "antd"; +import axios from "axios"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import cleanAxios from "../../utils/CleanAxios"; +import formatBytes from "../../utils/formatbytes"; +//import yauzl from "yauzl"; + +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyDownloadButton); + +export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, identifier }) { + const { t } = useTranslation(); + const [download, setDownload] = useState(null); + const [loading, setLoading] = useState(false); + + const imagesToDownload = [ + ...galleryImages.images.filter((image) => image.isSelected), + ...galleryImages.other.filter((image) => image.isSelected) + ]; + + function downloadProgress(progressEvent) { + setDownload((currentDownloadState) => { + return { + downloaded: progressEvent.loaded || 0, + speed: (progressEvent.loaded || 0) - ((currentDownloadState && currentDownloadState.downloaded) || 0) + }; + }); + } + function standardMediaDownload(bufferData) { + const a = document.createElement("a"); + const url = window.URL.createObjectURL(new Blob([bufferData])); + a.href = url; + a.download = `${identifier || "documents"}.zip`; + a.click(); + } + const handleDownload = async () => { + logImEXEvent("jobs_documents_download"); + setLoading(true); + const zipUrl = await axios({ + url: "/media/imgproxy/download", + method: "POST", + data: { documentids: imagesToDownload.map((_) => _.id) } + }); + + const theDownloadedZip = await cleanAxios({ + url: zipUrl.data.url, + method: "GET", + responseType: "arraybuffer", + onDownloadProgress: downloadProgress + }); + setLoading(false); + setDownload(null); + + standardMediaDownload(theDownloadedZip.data); + }; + + return ( + <> + + + ); +} diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx new file mode 100644 index 000000000..19b7d9352 --- /dev/null +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx @@ -0,0 +1,129 @@ +import { useApolloClient } from "@apollo/client"; +import { Button, Form, Popover, Space } from "antd"; +import axios from "axios"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; +import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries.js"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import JobSearchSelect from "../job-search-select/job-search-select.component.jsx"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyGalleryReassign); + +export function JobsDocumentsImgproxyGalleryReassign({ bodyshop, galleryImages, callback }) { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const notification = useNotification(); + + const selectedImages = useMemo(() => { + return [ + ...galleryImages.images.filter((image) => image.isSelected), + ...galleryImages.other.filter((image) => image.isSelected) + ]; + }, [galleryImages]); + const client = useApolloClient(); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + + const handleFinish = async ({ jobid }) => { + setLoading(true); + + //Check to see if the space remaining on the new job is sufficient. If it isn't cancel this. + const newJobData = await client.query({ + query: GET_DOC_SIZE_BY_JOB, + variables: { jobId: jobid } + }); + + const transferedDocSizeTotal = selectedImages.reduce((acc, val) => acc + val.size, 0); + + const shouldPreventTransfer = + bodyshop.jobsizelimit - newJobData.data.documents_aggregate.aggregate.sum.size < transferedDocSizeTotal; + + if (shouldPreventTransfer) { + notification.open({ + key: "cannotuploaddocuments", + type: "error", + message: t("documents.labels.reassign_limitexceeded_title"), + description: t("documents.labels.reassign_limitexceeded") + }); + setLoading(false); + return; + } + + const res = await axios.post("/media/imgproxy/rename", { + tojobid: jobid, + documents: selectedImages.map((i) => { + //Need to check if the current key folder is null, or another job. + const currentKeys = i.key.split("/"); + currentKeys[1] = jobid; + currentKeys.join("/"); + return { + id: i.id, + from: i.key, + to: currentKeys.join("/"), + extension: i.extension, + type: i.type + }; + }) + }); + //Add in confirmation & errors. + if (callback) callback(); + + if (res.errors) { + notification["error"]({ + message: t("documents.errors.updating", { + message: JSON.stringify(res.errors) + }) + }); + } + if (!res.mutationResult?.errors) { + notification["success"]({ + message: t("documents.successes.updated") + }); + } + setOpen(false); + setLoading(false); + }; + + const popContent = ( +
+
+ + + +
+ + + + +
+ ); + + return ( + + + + ); +} diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx new file mode 100644 index 000000000..0a9beacc1 --- /dev/null +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx @@ -0,0 +1,247 @@ +import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons"; +import { Button, Card, Col, Row, Space, Typography } from "antd"; +import axios from "axios"; +import { useEffect, useState } from "react"; +import { Gallery } from "react-grid-gallery"; +import { useTranslation } from "react-i18next"; +import Lightbox from "react-image-lightbox"; +import "react-image-lightbox/style.css"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import DocumentsUploadImgproxyComponent from "../documents-upload-imgproxy/documents-upload-imgproxy.component"; +import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; +import UpsellComponent, { upsellEnum } from "../upsell/upsell.component"; +import JobsDocumentsDownloadButton from "./jobs-document-imgproxy-gallery.download.component"; +import JobsDocumentsGalleryReassign from "./jobs-document-imgproxy-gallery.reassign.component"; +import JobsDocumentsDeleteButton from "./jobs-documents-imgproxy-gallery.delete.component"; +import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-imgproxy-gallery.selectall.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); +const mapDispatchToProps = (dispatch) => ({}); + +function JobsDocumentsImgproxyComponent({ + bodyshop, + data, + jobId, + refetch, + billId, + billsCallback, + totalSize, + downloadIdentifier, + ignoreSizeLimit +}) { + const [galleryImages, setgalleryImages] = useState({ images: [], other: [] }); + const { t } = useTranslation(); + const [modalState, setModalState] = useState({ open: false, index: 0 }); + + const fetchThumbnails = async () => { + const result = await axios.post("/media/imgproxy/thumbnails", { jobid: jobId }); + let documents = result.data.reduce( + (acc, value) => { + if (value.type.startsWith("image")) { + acc.images.push({ + src: value.thumbnailUrl, + fullsize: value.originalUrl, + height: 225, + width: 225, + isSelected: false, + key: value.key, + extension: value.extension, + id: value.id, + type: value.type, + size: value.size, + tags: [{ value: value.type, title: value.type }] + }); + } else { + const fileName = value.key.split("/").pop(); + acc.other.push({ + source: value.originalUrlViaProxyPath, + src: value.thumbnailUrl, + fullsize: value.presignedGetUrl, + tags: [ + { + value: fileName, + title: fileName + }, + + { value: value.type, title: value.type }, + ...(value.bill + ? [ + { + value: value.bill.vendor.name, + title: t("vendors.fields.name") + }, + { value: value.bill.date, title: t("bills.fields.date") }, + { + value: value.bill.invoice_number, + title: t("bills.fields.invoice_number") + } + ] + : []) + ], + height: 225, + width: 225, + isSelected: false, + extension: value.extension, + key: value.key, + id: value.id, + type: value.type, + size: value.size + }); + } + return acc; + }, + { images: [], other: [] } + ); + setgalleryImages(documents); + }; + useEffect(() => { + if (data) { + fetchThumbnails(); + } + }, [data, setgalleryImages, t]); + + const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); + const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); + return ( +
+ IMG PROXY COMPONENTS (DELETE ME) + + + + + + + + {!billId && } + + + {!hasMediaAccess && ( + + + + + + )} + + + + + + {hasMediaAccess && !hasMobileAccess && ( + + + + + + )} + + + { + setModalState({ open: true, index: index }); + // window.open( + // item.fullsize, + // "_blank", + // "toolbar=0,location=0,menubar=0" + // ); + }} + onSelect={(index, image) => { + setgalleryImages({ + ...galleryImages, + images: galleryImages.images.map((g, idx) => + index === idx ? { ...g, isSelected: !g.isSelected } : g + ) + }); + }} + /> + + + + + { + return { + backgroundImage: , + height: "100%", + width: "100%", + cursor: "pointer" + }; + }} + onClick={(index) => { + window.open(galleryImages.other[index].source, "_blank", "toolbar=0,location=0,menubar=0"); + }} + onSelect={(index) => { + setgalleryImages({ + ...galleryImages, + other: galleryImages.other.map((g, idx) => (index === idx ? { ...g, isSelected: !g.isSelected } : g)) + }); + }} + /> + + + {modalState.open && ( + { + const newWindow = window.open( + `${window.location.protocol}//${window.location.host}/edit?documentId=${ + galleryImages.images[modalState.index].id + }`, + "_blank", + "noopener,noreferrer" + ); + if (newWindow) newWindow.opener = null; + }} + /> + ]} + mainSrc={galleryImages.images[modalState.index].fullsize} + nextSrc={galleryImages.images[(modalState.index + 1) % galleryImages.images.length].fullsize} + prevSrc={ + galleryImages.images[(modalState.index + galleryImages.images.length - 1) % galleryImages.images.length] + .fullsize + } + onCloseRequest={() => setModalState({ open: false, index: 0 })} + onMovePrevRequest={() => + setModalState({ + ...modalState, + index: (modalState.index + galleryImages.images.length - 1) % galleryImages.images.length + }) + } + onMoveNextRequest={() => + setModalState({ + ...modalState, + index: (modalState.index + 1) % galleryImages.images.length + }) + } + /> + )} + +
+ ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyComponent); diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.container.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.container.jsx new file mode 100644 index 000000000..50dd4bbe9 --- /dev/null +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.container.jsx @@ -0,0 +1,29 @@ +import { useQuery } from "@apollo/client"; +import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries"; +import AlertComponent from "../alert/alert.component"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import JobDocuments from "./jobs-documents-imgproxy-gallery.component"; + +export default function JobsDocumentsImgproxyContainer({ jobId, billId, documentsList, billsCallback }) { + const { loading, error, data, refetch } = useQuery(GET_DOCUMENTS_BY_JOB, { + variables: { jobId: jobId }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + skip: !!billId + }); + + if (loading) return ; + if (error) return ; + + return ( + + ); +} diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx new file mode 100644 index 000000000..51ebfbf2b --- /dev/null +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx @@ -0,0 +1,68 @@ +import { QuestionCircleOutlined } from "@ant-design/icons"; +import { Button, Popconfirm } from "antd"; +import axios from "axios"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils.js"; +import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; +//Context: currentUserEmail, bodyshop, jobid, invoiceid + +export default function JobsDocumentsImgproxyDeleteButton({ galleryImages, deletionCallback }) { + const { t } = useTranslation(); + const notification = useNotification(); + + const imagesToDelete = [ + ...galleryImages.images.filter((image) => image.isSelected), + ...galleryImages.other.filter((image) => image.isSelected) + ]; + const [loading, setLoading] = useState(false); + + const handleDelete = async () => { + logImEXEvent("job_documents_delete", { count: imagesToDelete.length }); + try { + setLoading(true); + const res = await axios.post("/media/imgproxy/delete", { + ids: imagesToDelete.map((d) => d.id) + }); + + if (res.data.error) { + notification["error"]({ + message: t("documents.errors.deleting", { + error: JSON.stringify(res.data.error.response.errors) + }) + }); + } else { + notification.open({ + key: "docdeletedsuccesfully", + type: "success", + message: t("documents.successes.delete") + }); + + if (deletionCallback) deletionCallback(); + } + } catch (error) { + notification["error"]({ + message: t("documents.errors.deleting", { + error: error.message + }) + }); + } + setLoading(false); + }; + + return ( + } + onConfirm={handleDelete} + title={t("documents.labels.confirmdelete")} + okText={t("general.actions.delete")} + okButtonProps={{ danger: true }} + cancelText={t("general.actions.cancel")} + > + + + ); +} diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx new file mode 100644 index 000000000..ed89cc1ca --- /dev/null +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx @@ -0,0 +1,50 @@ +import { useEffect } from "react"; +import { Gallery } from "react-grid-gallery"; +import { useTranslation } from "react-i18next"; +import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents-imgproxy.utility"; + +function JobsDocumentImgproxyGalleryExternal({ + data, + + externalMediaState +}) { + const [galleryImages, setgalleryImages] = externalMediaState; + const { t } = useTranslation(); + + useEffect(() => { + let documents = data.reduce((acc, value) => { + if (value.type.startsWith("image")) { + acc.push({ + fullsize: GenerateSrcUrl(value), + src: GenerateThumbUrl(value), + thumbnailHeight: 225, + thumbnailWidth: 225, + isSelected: false, + key: value.key, + extension: value.extension, + id: value.id, + type: value.type, + tags: [{ value: value.type, title: value.type }], + size: value.size + }); + } + + return acc; + }, []); + setgalleryImages(documents); + }, [data, setgalleryImages, t]); + + return ( +
+ { + setgalleryImages(galleryImages.map((g, idx) => (index === idx ? { ...g, isSelected: !g.isSelected } : g))); + }} + /> +
+ ); +} + +export default JobsDocumentImgproxyGalleryExternal; diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.selectall.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.selectall.component.jsx new file mode 100644 index 000000000..25162e7d0 --- /dev/null +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.selectall.component.jsx @@ -0,0 +1,55 @@ +import { Button, Space } from "antd"; +import { useTranslation } from "react-i18next"; + +export default function JobsDocumentsImgproxyGallerySelectAllComponent({ galleryImages, setGalleryImages }) { + const { t } = useTranslation(); + + const handleSelectAll = () => { + setGalleryImages({ + ...galleryImages, + other: galleryImages.other.map((i) => { + return { ...i, isSelected: true }; + }), + images: galleryImages.images.map((i) => { + return { ...i, isSelected: true }; + }) + }); + }; + const handleSelectAllImages = () => { + setGalleryImages({ + ...galleryImages, + + images: galleryImages.images.map((i) => { + return { ...i, isSelected: true }; + }) + }); + }; + const handleSelectAllDocuments = () => { + setGalleryImages({ + ...galleryImages, + other: galleryImages.other.map((i) => { + return { ...i, isSelected: true }; + }) + }); + }; + const handleDeselectAll = () => { + setGalleryImages({ + ...galleryImages, + other: galleryImages.other.map((i) => { + return { ...i, isSelected: false }; + }), + images: galleryImages.images.map((i) => { + return { ...i, isSelected: false }; + }) + }); + }; + + return ( + + + + + + + ); +} diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.styles.scss b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.styles.scss new file mode 100644 index 000000000..6df357ffc --- /dev/null +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.styles.scss @@ -0,0 +1,10 @@ +/* you can make up upload button and sample style by using stylesheets */ +.ant-upload-select-picture-card i { + font-size: 32px; + color: #999; +} + +.ant-upload-select-picture-card .ant-upload-text { + margin-top: 8px; + color: #666; +} diff --git a/client/src/pages/temporary-docs/temporary-docs.component.jsx b/client/src/pages/temporary-docs/temporary-docs.component.jsx index 092011e65..95ff5cabe 100644 --- a/client/src/pages/temporary-docs/temporary-docs.component.jsx +++ b/client/src/pages/temporary-docs/temporary-docs.component.jsx @@ -2,6 +2,7 @@ import { useQuery } from "@apollo/client"; import React from "react"; import AlertComponent from "../../components/alert/alert.component"; import JobsDocumentsComponent from "../../components/jobs-documents-gallery/jobs-documents-gallery.component"; +import JobsDocumentsContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import { QUERY_TEMPORARY_DOCS } from "../../graphql/documents.queries"; @@ -22,7 +23,7 @@ export function TemporaryDocsComponent({ bodyshop }) { const { loading, error, data, refetch } = useQuery(QUERY_TEMPORARY_DOCS, { fetchPolicy: "network-only", nextFetchPolicy: "network-only", - skip: bodyshop.uselocalmediaserver + skip: bodyshop.uselocalmediaserver //TODO: Add skip if imgproxy is enabled. }); if (loading) return ; @@ -32,12 +33,14 @@ export function TemporaryDocsComponent({ bodyshop }) { return ; } return ( - + <> + + ); } diff --git a/package-lock.json b/package-lock.json index d0ea5ebcc..608a76ac4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,10 +15,12 @@ "@aws-sdk/client-secrets-manager": "^3.738.0", "@aws-sdk/client-ses": "^3.738.0", "@aws-sdk/credential-provider-node": "^3.738.0", + "@aws-sdk/lib-storage": "^3.743.0", "@aws-sdk/s3-request-presigner": "^3.731.1", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", + "archiver": "^7.0.1", "aws4": "^1.13.2", "axios": "^1.7.7", "better-queue": "^3.8.12", @@ -794,6 +796,37 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.743.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.743.0.tgz", + "integrity": "sha512-Rf/5sljlEJRVtB5C4UjLCOIcK2ODZet9rQsRtsn0bIc2byURbpOdqIGvfEcKWPayoXCS4dC/5bdjhL1zhZ0TMg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.2", + "@smithy/smithy-client": "^4.1.2", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.743.0" + } + }, + "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "node_modules/@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", @@ -2488,6 +2521,16 @@ "node": ">=14" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -3678,7 +3721,6 @@ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "license": "MIT", - "optional": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -3813,6 +3855,155 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "license": "ISC" }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", @@ -4090,12 +4281,25 @@ "js-md4": "^0.3.2" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4259,6 +4463,39 @@ "node": ">= 0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -4612,6 +4849,38 @@ "node": ">=18" } }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -4831,6 +5100,47 @@ "node": ">=10.0.0" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/crisp-status-reporter": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crisp-status-reporter/-/crisp-status-reporter-1.2.2.tgz", @@ -5965,11 +6275,19 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", - "optional": true, "engines": { "node": ">=6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -6828,6 +7146,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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==", + "license": "ISC" + }, "node_modules/graphql": { "version": "16.10.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", @@ -7098,6 +7422,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -8007,6 +8351,48 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8608,6 +8994,15 @@ "node": ">=6" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/notepack.io": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", @@ -9096,6 +9491,15 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -9306,6 +9710,36 @@ "node": ">= 6" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/recursive-diff": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/recursive-diff/-/recursive-diff-1.0.9.tgz", @@ -10356,6 +10790,16 @@ "node": ">= 0.8" } }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -10381,6 +10825,19 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -10652,6 +11109,17 @@ "node": ">=10" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tar/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -10754,6 +11222,15 @@ "rimraf": "bin.js" } }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -11766,6 +12243,36 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } } } } diff --git a/package.json b/package.json index c24e79f8e..91b4bc851 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,12 @@ "@aws-sdk/client-secrets-manager": "^3.738.0", "@aws-sdk/client-ses": "^3.738.0", "@aws-sdk/credential-provider-node": "^3.738.0", + "@aws-sdk/lib-storage": "^3.743.0", "@aws-sdk/s3-request-presigner": "^3.731.1", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", + "archiver": "^7.0.1", "aws4": "^1.13.2", "axios": "^1.7.7", "better-queue": "^3.8.12", diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index fa08614fb..a3bc38917 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2722,3 +2722,30 @@ exports.GET_DOCUMENTS_BY_JOB = ` } } }`; + +exports.QUERY_TEMPORARY_DOCS = ` query QUERY_TEMPORARY_DOCS { + documents(where: { jobid: { _is_null: true } }, order_by: { takenat: desc }) { + id + name + key + type + extension + size + takenat + } + }`; + +exports.GET_DOCUMENTS_BY_IDS = ` + query GET_DOCUMENTS_BY_IDS($documentIds: [uuid!]!) { + documents(where: {id: {_in: $documentIds}}, order_by: {takenat: desc}) { + id + name + key + type + extension + size + takenat + } +} + + `; diff --git a/server/media/imgprox-media.js b/server/media/imgprox-media.js index 2ee4d5d5b..4cb0b7d94 100644 --- a/server/media/imgprox-media.js +++ b/server/media/imgprox-media.js @@ -3,13 +3,30 @@ require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const logger = require("../utils/logger"); -const { S3Client, PutObjectCommand, GetObjectCommand } = require("@aws-sdk/client-s3"); +const { + S3Client, + PutObjectCommand, + GetObjectCommand, + CopyObjectCommand, + DeleteObjectCommand +} = require("@aws-sdk/client-s3"); +const { Upload } = require("@aws-sdk/lib-storage"); + const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); const crypto = require("crypto"); const { InstanceRegion } = require("../utils/instanceMgr"); -const { GET_DOCUMENTS_BY_JOB } = require("../graphql-client/queries"); -//TODO: Remove hardcoded values. -const imgproxyBaseUrl = process.env.IMGPROXY_BASE_URL; +const { + GET_DOCUMENTS_BY_JOB, + QUERY_TEMPORARY_DOCS, + GET_DOCUMENTS_BY_IDS, + DELETE_MEDIA_DOCUMENTS +} = require("../graphql-client/queries"); +const archiver = require("archiver"); +const stream = require("node:stream"); + +const imgproxyBaseUrl = + // `https://u4gzpp5wm437dnm75qa42tvza40fguqr.lambda-url.ca-central-1.on.aws` || //Direct Lambda function access to bypass CDN. + process.env.IMGPROXY_BASE_URL; const imgproxyKey = process.env.IMGPROXY_KEY; const imgproxySalt = process.env.IMGPROXY_SALT; const imgproxyDestinationBucket = process.env.IMGPROXY_DESTINATION_BUCKET; @@ -31,10 +48,13 @@ exports.generateSignedUploadUrls = async (req, res) => { const signedUrls = []; for (const filename of filenames) { - // TODO: Implement a different, unique file naming convention. const key = filename; //GenerateKey({ bodyshopid, jobid, filename }); const client = new S3Client({ region: InstanceRegion() }); - const command = new PutObjectCommand({ Bucket: imgproxyDestinationBucket, Key: key }); + const command = new PutObjectCommand({ + Bucket: imgproxyDestinationBucket, + Key: key, + StorageClass: "INTELLIGENT_TIERING" + }); const presignedUrl = await getSignedUrl(client, command, { expiresIn: 360 }); signedUrls.push({ filename, presignedUrl, key }); } @@ -61,11 +81,14 @@ exports.getThumbnailUrls = async (req, res) => { const { jobid, billid } = req.body; try { - //TODO: Query for all documents related to the job. - //Delayed as the key structure may change slightly from what it is currently and will require evaluating mobile components. + logger.log("imgproxy-thumbnails", "DEBUG", req.user?.email, jobid, { billid, jobid }); + //Delayed as the key structure may change slightly from what it is currently and will require evaluating mobile components. const client = req.userGraphQLClient; - const data = await client.request(GET_DOCUMENTS_BY_JOB, { jobId: jobid }); + //If there's no jobid and no billid, we're in temporary documents. + const data = await (jobid + ? client.request(GET_DOCUMENTS_BY_JOB, { jobId: jobid }) + : client.request(QUERY_TEMPORARY_DOCS)); const thumbResizeParams = `rs:fill:250:250:1/g:ce`; const s3client = new S3Client({ region: InstanceRegion() }); @@ -123,7 +146,9 @@ exports.getThumbnailUrls = async (req, res) => { res.json(proxiedUrls); //Iterate over them, build the link based on the media type, and return the array. } catch (error) { - logger.log("imgproxy-get-proxied-urls-error", "ERROR", req.user?.email, jobid, { + logger.log("imgproxy-thumbnails-error", "ERROR", req.user?.email, jobid, { + jobid, + billid, message: error.message, stack: error.stack }); @@ -137,20 +162,194 @@ exports.getBillFiles = async (req, res) => { exports.downloadFiles = async (req, res) => { //Given a series of document IDs or keys, generate a file (or a link) to download all images in bulk + const { jobid, billid, documentids } = req.body; + try { + logger.log("imgproxy-download", "DEBUG", req.user?.email, jobid, { billid, jobid, documentids }); + + //Delayed as the key structure may change slightly from what it is currently and will require evaluating mobile components. + const client = req.userGraphQLClient; + //Query for the keys of the document IDs + const data = await client.request(GET_DOCUMENTS_BY_IDS, { documentIds: documentids }); + //Using the Keys, get all of the S3 links, zip them, and send back to the client. + const s3client = new S3Client({ region: InstanceRegion() }); + const archiveStream = archiver("zip"); + archiveStream.on("error", (error) => { + console.error("Archival encountered an error:", error); + throw new Error(error); + }); + const passthrough = new stream.PassThrough(); + + archiveStream.pipe(passthrough); + for (const key of data.documents.map((d) => d.key)) { + const response = await s3client.send(new GetObjectCommand({ Bucket: imgproxyDestinationBucket, Key: key })); + // :: `response.Body` is a Buffer + console.log(path.basename(key)); + archiveStream.append(response.Body, { name: path.basename(key) }); + } + + archiveStream.finalize(); + + const archiveKey = `archives/${jobid}/archive-${new Date().toISOString()}.zip`; + + const parallelUploads3 = new Upload({ + client: s3client, + queueSize: 4, // optional concurrency configuration + leavePartsOnError: false, // optional manually handle dropped parts + params: { Bucket: imgproxyDestinationBucket, Key: archiveKey, Body: passthrough } + }); + + parallelUploads3.on("httpUploadProgress", (progress) => { + console.log(progress); + }); + + const uploadResult = await parallelUploads3.done(); + //Generate the presigned URL to download it. + const presignedUrl = await getSignedUrl( + s3client, + new GetObjectCommand({ Bucket: imgproxyDestinationBucket, Key: archiveKey }), + { expiresIn: 360 } + ); + + res.json({ success: true, url: presignedUrl }); + //Iterate over them, build the link based on the media type, and return the array. + } catch (error) { + logger.log("imgproxy-thumbnails-error", "ERROR", req.user?.email, jobid, { + jobid, + billid, + message: error.message, + stack: error.stack + }); + res.status(400).json({ message: error.message, stack: error.stack }); + } }; exports.deleteFiles = async (req, res) => { //Mark a file for deletion in s3. Lifecycle deletion will actually delete the copy in the future. //Mark as deleted from the documents section of the database. + const { ids } = req.body; + try { + logger.log("imgproxy-delete-files", "DEBUG", req.user.email, null, { ids }); + const client = req.userGraphQLClient; + + //Do this to make sure that they are only deleting things that they have access to + const data = await client.request(GET_DOCUMENTS_BY_IDS, { documentIds: ids }); + + const s3client = new S3Client({ region: InstanceRegion() }); + + const deleteTransactions = []; + data.documents.forEach((document) => { + deleteTransactions.push( + (async () => { + try { + // Delete the original object + const deleteResult = await s3client.send( + new DeleteObjectCommand({ + Bucket: imgproxyDestinationBucket, + Key: document.key + }) + ); + + return document; + } catch (error) { + return { document, error: error, bucket: imgproxyDestinationBucket }; + } + })() + ); + }); + + const result = await Promise.all(deleteTransactions); + console.log("*** ~ file: imgprox-media.js:260 ~ exports.deleteFiles ~ result:", result); + const errors = result.filter((d) => d.error); + + //Delete only the succesful deletes. + const deleteMutationResult = await client.request(DELETE_MEDIA_DOCUMENTS, { + ids: result.filter((t) => !t.error).map((d) => d.id) + }); + + res.json({ errors, deleteMutationResult }); + } catch (error) { + logger.log("imgproxy-delete-files-error", "ERROR", req.user.email, null, { + ids, + message: error.message, + stack: error.stack + }); + res.status(400).json({ message: error.message, stack: error.stack }); + } }; -//Gerneate a key for the s3 bucket by popping off the extension, add a timestamp, and add back the extension. -//This is to prevent any collisions/duplicates in the bucket. -function GenerateKey({ bodyshopid, jobid, filename }) { - let nameArray = filename.split("."); - let extension = nameArray.pop(); - return `${bodyshopid}/${jobid}/${nameArray.join(".")}-${Date.now()}`; -} +exports.moveFiles = async (req, res) => { + const { documents, tojobid } = req.body; + try { + logger.log("imgproxy-move-files", "DEBUG", req.user.email, null, { documents, tojobid }); + const s3client = new S3Client({ region: InstanceRegion() }); + + const moveTransactions = []; + documents.forEach((document) => { + moveTransactions.push( + (async () => { + try { + // Copy the object to the new key + const copyresult = await s3client.send( + new CopyObjectCommand({ + Bucket: imgproxyDestinationBucket, + CopySource: `${imgproxyDestinationBucket}/${document.from}`, + Key: document.to, + StorageClass: "INTELLIGENT_TIERING" + }) + ); + + // Delete the original object + const deleteResult = await s3client.send( + new DeleteObjectCommand({ + Bucket: imgproxyDestinationBucket, + Key: document.from + }) + ); + + return document; + } catch (error) { + return { id: document.id, from: document.from, error: error, bucket: imgproxyDestinationBucket }; + } + })() + ); + }); + + const result = await Promise.all(moveTransactions); + const errors = result.filter((d) => d.error); + + let mutations = ""; + result + .filter((d) => !d.error) + .forEach((d, idx) => { + //Create mutation text + mutations = + mutations + + ` + update_doc${idx}:update_documents_by_pk(pk_columns: { id: "${d.id}" }, _set: {key: "${d.to}", jobid: "${tojobid}"}){ + id + } + `; + }); + + const client = req.userGraphQLClient; + if (mutations !== "") { + const mutationResult = await client.request(`mutation { + ${mutations} + }`); + res.json({ errors, mutationResult }); + } else { + res.json({ errors: "No images were succesfully moved on remote server. " }); + } + } catch (error) { + logger.log("imgproxy-move-files-error", "ERROR", req.user.email, null, { + documents, + tojobid, + message: error.message, + stack: error.stack + }); + res.status(400).json({ message: error.message, stack: error.stack }); + } +}; function base64UrlEncode(str) { return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); diff --git a/server/routes/mediaRoutes.js b/server/routes/mediaRoutes.js index 9718b85d1..293ac51a0 100644 --- a/server/routes/mediaRoutes.js +++ b/server/routes/mediaRoutes.js @@ -1,7 +1,13 @@ const express = require("express"); const router = express.Router(); const { createSignedUploadURL, downloadFiles, renameKeys, deleteFiles } = require("../media/media"); -const { generateSignedUploadUrls, getThumbnailUrls } = require("../media/imgprox-media"); +const { + generateSignedUploadUrls, + getThumbnailUrls, + downloadFiles: downloadFilesImgproxy, + moveFiles, + deleteFiles: deleteFilesImgproxy +} = require("../media/imgprox-media"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); @@ -13,7 +19,10 @@ router.post("/download", downloadFiles); router.post("/rename", renameKeys); router.post("/delete", deleteFiles); -router.post("/proxy/sign", generateSignedUploadUrls); -router.post("/proxy/thumbnails", getThumbnailUrls); +router.post("/imgproxy/sign", generateSignedUploadUrls); +router.post("/imgproxy/thumbnails", getThumbnailUrls); +router.post("/imgproxy/download", downloadFilesImgproxy); +router.post("/imgproxy/rename", moveFiles); +router.post("/imgproxy/delete", deleteFilesImgproxy); module.exports = router; From 66671385d04e245876dfa9def726005d5252cc36 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 25 Feb 2025 11:15:29 -0800 Subject: [PATCH 05/33] IO-3092 resolve thumbnail fetching --- .../jobs-documents-imgproxy-gallery.component.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx index 0a9beacc1..d102e263f 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx @@ -108,7 +108,6 @@ function JobsDocumentsImgproxyComponent({ const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); return (
- IMG PROXY COMPONENTS (DELETE ME) @@ -126,8 +125,13 @@ function JobsDocumentsImgproxyComponent({ - - {!billId && } + + {!billId && ( + + )} {!hasMediaAccess && ( @@ -143,7 +147,7 @@ function JobsDocumentsImgproxyComponent({ jobId={jobId} totalSize={totalSize} billId={billId} - callbackAfterUpload={billsCallback || refetch} + callbackAfterUpload={billsCallback || fetchThumbnails || refetch} ignoreSizeLimit={ignoreSizeLimit} /> From 25b289b65de3be0a1cf8226787eadb21a5fe8f85 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 26 Feb 2025 20:56:13 -0800 Subject: [PATCH 06/33] IO-3092 add messaging support for imgproxy --- .../chat-media-selector.component.jsx | 24 +++- .../jobs-documents-gallery.container.jsx | 30 +++- ...s-documents-imgproxy-gallery.component.jsx | 134 ++++++++++-------- ...ts-imgproxy-gallery.external.component.jsx | 36 ++--- .../temporary-docs.component.jsx | 14 +- 5 files changed, 136 insertions(+), 102 deletions(-) diff --git a/client/src/components/chat-media-selector/chat-media-selector.component.jsx b/client/src/components/chat-media-selector/chat-media-selector.component.jsx index 0526fde43..cc7a56b73 100644 --- a/client/src/components/chat-media-selector/chat-media-selector.component.jsx +++ b/client/src/components/chat-media-selector/chat-media-selector.component.jsx @@ -1,7 +1,8 @@ import { PictureFilled } from "@ant-design/icons"; import { useQuery } from "@apollo/client"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Badge, Popover } from "antd"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -9,6 +10,7 @@ import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component"; +import JobsDocumentImgproxyGalleryExternal from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component"; import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; @@ -23,6 +25,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector); export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, conversation }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); + const { + treatments: { Imgproxy } + } = useSplitTreatments({ + attributes: {}, + names: ["Imgproxy"], + splitKey: bodyshop && bodyshop.imexshopid + }); const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, { fetchPolicy: "network-only", @@ -42,6 +51,9 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c setSelectedMedia([]); }, [setSelectedMedia, conversation]); + + //Knowingly taking on the technical debt of poor implementation below. + //Cloudinary will be removed once the migration is completed. const content = (
{loading && } @@ -49,13 +61,19 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c {selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
{t("messaging.labels.maxtenimages")}
) : null} - {!bodyshop.uselocalmediaserver && data && ( + {Imgproxy.treatment === "on" && !bodyshop.uselocalmediaserver && data && ( + + )} + {Imgproxy.treatment !== "on" && !bodyshop.uselocalmediaserver && data && ( )} - {bodyshop.uselocalmediaserver && open && ( + {Imgproxy.treatment !== "on" && bodyshop.uselocalmediaserver && open && ( ({}); +export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsContainer); -export default function JobsDocumentsContainer({ +export function JobsDocumentsContainer({ jobId, billId, documentsList, billsCallback, refetchOverride, - ignoreSizeLimit + ignoreSizeLimit, + bodyshop }) { - //TODO Add a checker to see whether we should use the img proxy side, or this side. - const useImgProxy = true; + const { + treatments: { Imgproxy } + } = useSplitTreatments({ + attributes: {}, + names: ["Imgproxy"], + splitKey: bodyshop && bodyshop.imexshopid + }); + const { loading, error, data, refetch } = useQuery(GET_DOCUMENTS_BY_JOB, { variables: { jobId: jobId }, fetchPolicy: "network-only", nextFetchPolicy: "network-only", - skip: useImgProxy || !!billId + skip: Imgproxy.treatment === "on" || !!billId }); if (loading) return ; if (error) return ; - if (useImgProxy) { + if (Imgproxy.treatment === "on") { return ( { - const result = await axios.post("/media/imgproxy/thumbnails", { jobid: jobId }); - let documents = result.data.reduce( - (acc, value) => { - if (value.type.startsWith("image")) { - acc.images.push({ - src: value.thumbnailUrl, - fullsize: value.originalUrl, - height: 225, - width: 225, - isSelected: false, - key: value.key, - extension: value.extension, - id: value.id, - type: value.type, - size: value.size, - tags: [{ value: value.type, title: value.type }] - }); - } else { - const fileName = value.key.split("/").pop(); - acc.other.push({ - source: value.originalUrlViaProxyPath, - src: value.thumbnailUrl, - fullsize: value.presignedGetUrl, - tags: [ - { - value: fileName, - title: fileName - }, - - { value: value.type, title: value.type }, - ...(value.bill - ? [ - { - value: value.bill.vendor.name, - title: t("vendors.fields.name") - }, - { value: value.bill.date, title: t("bills.fields.date") }, - { - value: value.bill.invoice_number, - title: t("bills.fields.invoice_number") - } - ] - : []) - ], - height: 225, - width: 225, - isSelected: false, - extension: value.extension, - key: value.key, - id: value.id, - type: value.type, - size: value.size - }); - } - return acc; - }, - { images: [], other: [] } - ); - setgalleryImages(documents); + const fetchThumbnails = () => { + fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId }); }; + useEffect(() => { if (data) { fetchThumbnails(); } - }, [data, setgalleryImages, t]); + }, [data, setgalleryImages]); const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); @@ -249,3 +193,69 @@ function JobsDocumentsImgproxyComponent({ } export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyComponent); + +export const fetchImgproxyThumbnails = async ({ setStateCallback, jobId, imagesOnly }) => { + const result = await axios.post("/media/imgproxy/thumbnails", { jobid: jobId }); + let documents = result.data.reduce( + (acc, value) => { + if (value.type.startsWith("image")) { + acc.images.push({ + src: value.thumbnailUrl, + fullsize: value.originalUrl, + height: 225, + width: 225, + isSelected: false, + key: value.key, + extension: value.extension, + id: value.id, + type: value.type, + size: value.size, + tags: [{ value: value.type, title: value.type }] + }); + } else { + const fileName = value.key.split("/").pop(); + acc.other.push({ + source: value.originalUrlViaProxyPath, + src: value.thumbnailUrl, + fullsize: value.presignedGetUrl, + tags: [ + { + value: fileName, + title: fileName + }, + + { value: value.type, title: value.type }, + ...(value.bill + ? [ + { + value: value.bill.vendor.name, + title: i18n.t("vendors.fields.name") + }, + { value: value.bill.date, title: i18n.t("bills.fields.date") }, + { + value: value.bill.invoice_number, + title: i18n.t("bills.fields.invoice_number") + } + ] + : []) + ], + height: 225, + width: 225, + isSelected: false, + extension: value.extension, + key: value.key, + id: value.id, + type: value.type, + size: value.size + }); + } + return acc; + }, + { images: [], other: [] } + ); + if (imagesOnly) { + setStateCallback(documents.images); + } else { + setStateCallback(documents); + } +}; diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx index ed89cc1ca..21c8059f9 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx @@ -1,38 +1,20 @@ import { useEffect } from "react"; import { Gallery } from "react-grid-gallery"; import { useTranslation } from "react-i18next"; -import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents-imgproxy.utility"; +import { fetchImgproxyThumbnails } from "./jobs-documents-imgproxy-gallery.component"; +//import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents-imgproxy.utility"; -function JobsDocumentImgproxyGalleryExternal({ - data, - - externalMediaState -}) { +function JobsDocumentImgproxyGalleryExternal({ jobId, externalMediaState }) { const [galleryImages, setgalleryImages] = externalMediaState; const { t } = useTranslation(); - useEffect(() => { - let documents = data.reduce((acc, value) => { - if (value.type.startsWith("image")) { - acc.push({ - fullsize: GenerateSrcUrl(value), - src: GenerateThumbUrl(value), - thumbnailHeight: 225, - thumbnailWidth: 225, - isSelected: false, - key: value.key, - extension: value.extension, - id: value.id, - type: value.type, - tags: [{ value: value.type, title: value.type }], - size: value.size - }); - } + const fetchThumbnails = () => { + fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId, imagesOnly: true }); + }; - return acc; - }, []); - setgalleryImages(documents); - }, [data, setgalleryImages, t]); + useEffect(() => { + fetchThumbnails(); + }, [jobId]); return (
diff --git a/client/src/pages/temporary-docs/temporary-docs.component.jsx b/client/src/pages/temporary-docs/temporary-docs.component.jsx index 95ff5cabe..26948d7a4 100644 --- a/client/src/pages/temporary-docs/temporary-docs.component.jsx +++ b/client/src/pages/temporary-docs/temporary-docs.component.jsx @@ -1,15 +1,15 @@ import { useQuery } from "@apollo/client"; import React from "react"; import AlertComponent from "../../components/alert/alert.component"; -import JobsDocumentsComponent from "../../components/jobs-documents-gallery/jobs-documents-gallery.component"; import JobsDocumentsContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import { QUERY_TEMPORARY_DOCS } from "../../graphql/documents.queries"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { selectBodyshop } from "../../redux/user/user.selectors"; import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -20,10 +20,18 @@ const mapDispatchToProps = (dispatch) => ({ export default connect(mapStateToProps, mapDispatchToProps)(TemporaryDocsComponent); export function TemporaryDocsComponent({ bodyshop }) { + const { + treatments: { Imgproxy } + } = useSplitTreatments({ + attributes: {}, + names: ["Imgproxy"], + splitKey: bodyshop && bodyshop.imexshopid + }); + const { loading, error, data, refetch } = useQuery(QUERY_TEMPORARY_DOCS, { fetchPolicy: "network-only", nextFetchPolicy: "network-only", - skip: bodyshop.uselocalmediaserver //TODO: Add skip if imgproxy is enabled. + skip: Imgproxy.treatment === "on" }); if (loading) return ; From fa29bd609f223dbd76bce6d41bf85bfc2d91d546 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 27 Feb 2025 12:15:36 -0800 Subject: [PATCH 07/33] IO-3092 Additional comments, email compatibility. --- .../chat-media-selector.component.jsx | 55 ++++++++++++------- .../documents-upload-imgproxy.utility.js | 2 +- .../email-documents.component.jsx | 51 +++++++++++++---- ...bs-document-gallery.download.component.jsx | 8 ++- ...bs-document-gallery.reassign.component.jsx | 8 ++- .../jobs-documents-gallery.component.jsx | 8 ++- .../jobs-documents-gallery.container.jsx | 8 ++- ...obs-documents-gallery.delete.component.jsx | 9 ++- ...s-documents-gallery.external.component.jsx | 7 +++ ...-documents-gallery.selectall.component.jsx | 8 +++ ...nt-imgproxy-gallery.download.component.jsx | 9 +++ ...nt-imgproxy-gallery.reassign.component.jsx | 8 ++- ...s-documents-imgproxy-gallery.component.jsx | 8 ++- ...s-documents-imgproxy-gallery.container.jsx | 8 +++ ...ents-imgproxy-gallery.delete.component.jsx | 10 +++- ...ts-imgproxy-gallery.external.component.jsx | 9 ++- ...s-imgproxy-gallery.selectall.component.jsx | 8 +++ .../{imgprox-media.js => imgproxy-media.js} | 15 +---- server/routes/mediaRoutes.js | 2 +- 19 files changed, 185 insertions(+), 56 deletions(-) rename server/media/{imgprox-media.js => imgproxy-media.js} (95%) diff --git a/client/src/components/chat-media-selector/chat-media-selector.component.jsx b/client/src/components/chat-media-selector/chat-media-selector.component.jsx index cc7a56b73..b7e7d64a3 100644 --- a/client/src/components/chat-media-selector/chat-media-selector.component.jsx +++ b/client/src/components/chat-media-selector/chat-media-selector.component.jsx @@ -51,9 +51,10 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c setSelectedMedia([]); }, [setSelectedMedia, conversation]); - - //Knowingly taking on the technical debt of poor implementation below. - //Cloudinary will be removed once the migration is completed. + //Knowingly taking on the technical debt of poor implementation below. Done this way to avoid an edge case where no component may be displayed. + //Cloudinary will be removed once the migration is completed. + //If Imageproxy is on, rely only on the LMS selector + //If not on, use the old methods. const content = (
{loading && } @@ -61,23 +62,37 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c {selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
{t("messaging.labels.maxtenimages")}
) : null} - {Imgproxy.treatment === "on" && !bodyshop.uselocalmediaserver && data && ( - - )} - {Imgproxy.treatment !== "on" && !bodyshop.uselocalmediaserver && data && ( - - )} - {Imgproxy.treatment !== "on" && bodyshop.uselocalmediaserver && open && ( - + + {Imgproxy.treatment === "on" ? ( + <> + {!bodyshop.uselocalmediaserver && ( + + )} + {bodyshop.uselocalmediaserver && open && ( + + )} + + ) : ( + <> + {!bodyshop.uselocalmediaserver && data && ( + + )} + {bodyshop.uselocalmediaserver && open && ( + + )} + )}
); diff --git a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js index 184cf86c6..ed64f5efb 100644 --- a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js +++ b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js @@ -74,7 +74,7 @@ export const uploadToS3 = async ( const exif = await exifr.parse(file); takenat = exif && exif.DateTimeOriginal; } catch (error) { - console.log("Unable to parse image file for EXIF Data"); + console.log("Unable to parse image file for EXIF Data", error.message); } } diff --git a/client/src/components/email-documents/email-documents.component.jsx b/client/src/components/email-documents/email-documents.component.jsx index d3c6b20da..7e17d5565 100644 --- a/client/src/components/email-documents/email-documents.component.jsx +++ b/client/src/components/email-documents/email-documents.component.jsx @@ -10,6 +10,8 @@ import AlertComponent from "../alert/alert.component"; import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component"; import JobsDocumentsLocalGalleryExternalComponent from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import JobsDocumentImgproxyGalleryExternal from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -23,6 +25,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(EmailDocumentsCompon export function EmailDocumentsComponent({ emailConfig, form, selectedMediaState, bodyshop }) { const { t } = useTranslation(); + const { + treatments: { Imgproxy } + } = useSplitTreatments({ + attributes: {}, + names: ["Imgproxy"], + splitKey: bodyshop && bodyshop.imexshopid + }); const [selectedMedia, setSelectedMedia] = selectedMediaState; const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, { @@ -46,17 +55,37 @@ export function EmailDocumentsComponent({ emailConfig, form, selectedMediaState, 10485760 - new Blob([form.getFieldValue("html")]).size ? (
{t("general.errors.sizelimit")}
) : null} - {!bodyshop.uselocalmediaserver && data && ( - - )} - {bodyshop.uselocalmediaserver && ( - + + {Imgproxy.treatment === "on" ? ( + <> + {!bodyshop.uselocalmediaserver && data && ( + + )} + {bodyshop.uselocalmediaserver && ( + + )} + + ) : ( + <> + {!bodyshop.uselocalmediaserver && data && ( + + )} + {bodyshop.uselocalmediaserver && ( + + )} + )}
); diff --git a/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx b/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx index f85db7c8c..cbc89ab38 100644 --- a/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-document-gallery.download.component.jsx @@ -19,7 +19,13 @@ const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsDownloadButton); - +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Imgproxy code. This code will be removed once the imgproxy migration is completed. +################################################################################################ +*/ export function JobsDocumentsDownloadButton({ bodyshop, galleryImages, identifier }) { const { t } = useTranslation(); const [download, setDownload] = useState(null); diff --git a/client/src/components/jobs-documents-gallery/jobs-document-gallery.reassign.component.jsx b/client/src/components/jobs-documents-gallery/jobs-document-gallery.reassign.component.jsx index 33f2e964a..51c889729 100644 --- a/client/src/components/jobs-documents-gallery/jobs-document-gallery.reassign.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-document-gallery.reassign.component.jsx @@ -17,7 +17,13 @@ const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsGalleryReassign); - +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Imgproxy code. This code will be removed once the imgproxy migration is completed. +################################################################################################ +*/ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages, callback }) { const { t } = useTranslation(); const [form] = Form.useForm(); diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx index fdc919320..58adaccad 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx @@ -22,7 +22,13 @@ const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); - +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Imgproxy code. This code will be removed once the imgproxy migration is completed. +################################################################################################ +*/ function JobsDocumentsComponent({ bodyshop, data, diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.container.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.container.jsx index 3c2cf3843..5bbd95f94 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.container.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.container.jsx @@ -14,7 +14,13 @@ const mapStateToProps = createStructuredSelector({ }); const mapDispatchToProps = (dispatch) => ({}); export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsContainer); - +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Imgproxy code. This code will be removed once the imgproxy migration is completed. +################################################################################################ +*/ export function JobsDocumentsContainer({ jobId, billId, diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx index 5cd1fcb06..4b92e4c99 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.delete.component.jsx @@ -5,8 +5,13 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; -//Context: currentUserEmail, bodyshop, jobid, invoiceid - +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Imgproxy code. This code will be removed once the imgproxy migration is completed. +################################################################################################ +*/ export default function JobsDocumentsDeleteButton({ galleryImages, deletionCallback }) { const { t } = useTranslation(); const notification = useNotification(); diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.external.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.external.component.jsx index 940598052..d4340bb98 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.external.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.external.component.jsx @@ -3,6 +3,13 @@ import { Gallery } from "react-grid-gallery"; import { useTranslation } from "react-i18next"; import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility"; +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Imgproxy code. This code will be removed once the imgproxy migration is completed. +################################################################################################ +*/ function JobsDocumentGalleryExternal({ data, diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.selectall.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.selectall.component.jsx index 122ce6236..157e04dca 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.selectall.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.selectall.component.jsx @@ -2,6 +2,14 @@ import { Button, Space } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Imgproxy code. This code will be removed once the imgproxy migration is completed. +################################################################################################ +*/ + export default function JobsDocumentsGallerySelectAllComponent({ galleryImages, setGalleryImages }) { const { t } = useTranslation(); diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx index 2d649bcc9..6c08936dc 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx @@ -17,6 +17,15 @@ const mapStateToProps = createStructuredSelector({ const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); + +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. +################################################################################################ +*/ + export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyDownloadButton); export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, identifier }) { diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx index 19b7d9352..4ce2d12a2 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx @@ -17,7 +17,13 @@ const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyGalleryReassign); - +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. +################################################################################################ +*/ export function JobsDocumentsImgproxyGalleryReassign({ bodyshop, galleryImages, callback }) { const { t } = useTranslation(); const [form] = Form.useForm(); diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx index b28d3f1bd..156362ff1 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx @@ -22,7 +22,13 @@ const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({}); - +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. +################################################################################################ +*/ function JobsDocumentsImgproxyComponent({ bodyshop, data, diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.container.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.container.jsx index 50dd4bbe9..9191b262c 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.container.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.container.jsx @@ -4,6 +4,14 @@ import AlertComponent from "../alert/alert.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import JobDocuments from "./jobs-documents-imgproxy-gallery.component"; +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. +################################################################################################ +*/ + export default function JobsDocumentsImgproxyContainer({ jobId, billId, documentsList, billsCallback }) { const { loading, error, data, refetch } = useQuery(GET_DOCUMENTS_BY_JOB, { variables: { jobId: jobId }, diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx index 51ebfbf2b..72143531b 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx @@ -5,7 +5,15 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils.js"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; -//Context: currentUserEmail, bodyshop, jobid, invoiceid + + +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. +################################################################################################ +*/ export default function JobsDocumentsImgproxyDeleteButton({ galleryImages, deletionCallback }) { const { t } = useTranslation(); diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx index 21c8059f9..75943dceb 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx @@ -2,7 +2,14 @@ import { useEffect } from "react"; import { Gallery } from "react-grid-gallery"; import { useTranslation } from "react-i18next"; import { fetchImgproxyThumbnails } from "./jobs-documents-imgproxy-gallery.component"; -//import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents-imgproxy.utility"; + +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. +################################################################################################ +*/ function JobsDocumentImgproxyGalleryExternal({ jobId, externalMediaState }) { const [galleryImages, setgalleryImages] = externalMediaState; diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.selectall.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.selectall.component.jsx index 25162e7d0..4d4b0b32e 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.selectall.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.selectall.component.jsx @@ -1,6 +1,14 @@ import { Button, Space } from "antd"; import { useTranslation } from "react-i18next"; +/* +################################################################################################ + Developer Note: + Known Technical Debt Item + Modifications to this code requires complementary changes to the Cloudinary code. Cloudinary code will be removed upon completed migration. +################################################################################################ +*/ + export default function JobsDocumentsImgproxyGallerySelectAllComponent({ galleryImages, setGalleryImages }) { const { t } = useTranslation(); diff --git a/server/media/imgprox-media.js b/server/media/imgproxy-media.js similarity index 95% rename from server/media/imgprox-media.js rename to server/media/imgproxy-media.js index 4cb0b7d94..fdb313984 100644 --- a/server/media/imgprox-media.js +++ b/server/media/imgproxy-media.js @@ -24,9 +24,7 @@ const { const archiver = require("archiver"); const stream = require("node:stream"); -const imgproxyBaseUrl = - // `https://u4gzpp5wm437dnm75qa42tvza40fguqr.lambda-url.ca-central-1.on.aws` || //Direct Lambda function access to bypass CDN. - process.env.IMGPROXY_BASE_URL; +const imgproxyBaseUrl = process.env.IMGPROXY_BASE_URL; // `https://u4gzpp5wm437dnm75qa42tvza40fguqr.lambda-url.ca-central-1.on.aws` //Direct Lambda function access to bypass CDN. const imgproxyKey = process.env.IMGPROXY_KEY; const imgproxySalt = process.env.IMGPROXY_SALT; const imgproxyDestinationBucket = process.env.IMGPROXY_DESTINATION_BUCKET; @@ -38,17 +36,9 @@ exports.generateSignedUploadUrls = async (req, res) => { try { logger.log("imgproxy-upload-start", "DEBUG", req.user?.email, jobid, { filenames, bodyshopid, jobid }); - //TODO: Ensure that the user has access to the given bodyshopid. - //This can be done by querying associations, or, maintaining a REDIS cache of user permissions. - const hasAccess = true; //TODO: Ensure this is not hardcoded. - if (!hasAccess) { - res.send(403); - return; - } - const signedUrls = []; for (const filename of filenames) { - const key = filename; //GenerateKey({ bodyshopid, jobid, filename }); + const key = filename; const client = new S3Client({ region: InstanceRegion() }); const command = new PutObjectCommand({ Bucket: imgproxyDestinationBucket, @@ -258,7 +248,6 @@ exports.deleteFiles = async (req, res) => { }); const result = await Promise.all(deleteTransactions); - console.log("*** ~ file: imgprox-media.js:260 ~ exports.deleteFiles ~ result:", result); const errors = result.filter((d) => d.error); //Delete only the succesful deletes. diff --git a/server/routes/mediaRoutes.js b/server/routes/mediaRoutes.js index 293ac51a0..0fcfdd3c5 100644 --- a/server/routes/mediaRoutes.js +++ b/server/routes/mediaRoutes.js @@ -7,7 +7,7 @@ const { downloadFiles: downloadFilesImgproxy, moveFiles, deleteFiles: deleteFilesImgproxy -} = require("../media/imgprox-media"); +} = require("../media/imgproxy-media"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); From f13a70a22f020f0f32a97babf0aec6b568ed2f16 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 27 Feb 2025 12:17:26 -0800 Subject: [PATCH 08/33] IO-3092 Improve import name in routes. --- server/routes/mediaRoutes.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/routes/mediaRoutes.js b/server/routes/mediaRoutes.js index 0fcfdd3c5..59ee836ec 100644 --- a/server/routes/mediaRoutes.js +++ b/server/routes/mediaRoutes.js @@ -2,10 +2,10 @@ const express = require("express"); const router = express.Router(); const { createSignedUploadURL, downloadFiles, renameKeys, deleteFiles } = require("../media/media"); const { - generateSignedUploadUrls, - getThumbnailUrls, + generateSignedUploadUrls: createSignedUploadURLImgproxy, + getThumbnailUrls: getThumbnailUrlsImgproxy, downloadFiles: downloadFilesImgproxy, - moveFiles, + moveFiles: moveFilesImgproxy, deleteFiles: deleteFilesImgproxy } = require("../media/imgproxy-media"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); @@ -19,10 +19,10 @@ router.post("/download", downloadFiles); router.post("/rename", renameKeys); router.post("/delete", deleteFiles); -router.post("/imgproxy/sign", generateSignedUploadUrls); -router.post("/imgproxy/thumbnails", getThumbnailUrls); +router.post("/imgproxy/sign", createSignedUploadURLImgproxy); +router.post("/imgproxy/thumbnails", getThumbnailUrlsImgproxy); router.post("/imgproxy/download", downloadFilesImgproxy); -router.post("/imgproxy/rename", moveFiles); +router.post("/imgproxy/rename", moveFilesImgproxy); router.post("/imgproxy/delete", deleteFilesImgproxy); module.exports = router; From ace0039429e7bb8c7001e070b775cbfaf5a0238c Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 27 Feb 2025 13:54:16 -0800 Subject: [PATCH 09/33] IO-3092 Address PR concerns. --- .../documents-upload-imgproxy.component.jsx | 3 +- .../documents-upload-imgproxy.utility.js | 125 ++++++++++-------- ...nt-imgproxy-gallery.reassign.component.jsx | 11 +- ...s-documents-imgproxy-gallery.component.jsx | 25 ++-- ...ents-imgproxy-gallery.delete.component.jsx | 11 +- server/graphql-client/queries.js | 4 +- 6 files changed, 96 insertions(+), 83 deletions(-) diff --git a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.component.jsx b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.component.jsx index 259ca5831..f71760e3b 100644 --- a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.component.jsx +++ b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.component.jsx @@ -67,9 +67,8 @@ export function DocumentsUploadImgproxyComponent({ //Check to see if old files plus newly uploaded ones will be too much. if (shouldStopUpload) { - notification.open({ + notification.error({ key: "cannotuploaddocuments", - type: "error", message: t("documents.labels.upload_limitexceeded_title"), description: t("documents.labels.upload_limitexceeded") }); diff --git a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js index ed64f5efb..8fa7cb001 100644 --- a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js +++ b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js @@ -5,6 +5,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; import { axiosAuthInterceptorId } from "../../utils/CleanAxios"; import client from "../../utils/GraphQLClient"; +import { error } from "logrocket"; //Context: currentUserEmail, bodyshop, jobid, invoiceid @@ -13,17 +14,26 @@ var cleanAxios = axios.create(); cleanAxios.interceptors.request.eject(axiosAuthInterceptorId); export const handleUpload = (ev, context, notification) => { - logImEXEvent("document_upload", { filetype: ev.file.type }); + logImEXEvent("document_upload", { filetype: ev.file?.type }); const { onError, onSuccess, onProgress } = ev; const { bodyshop, jobId } = context; - const fileName = ev.file.name || ev.filename; + const fileName = ev.file?.name || ev.filename; let extension = fileName.split(".").pop(); let key = `${bodyshop.id}/${jobId}/${replaceAccents(fileName).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}.${extension}`; - uploadToS3(key, extension, ev.file.type, ev.file, onError, onSuccess, onProgress, context, notification); + uploadToS3(key, extension, ev.file.type, ev.file, onError, onSuccess, onProgress, context, notification).catch( + (error) => { + console.error("Error uploading file to S3", error); + notification.error({ + message: i18n.t("documents.errors.insert", { + message: error.message + }) + }); + } + ); }; //Handles only 1 file at a time. @@ -49,7 +59,7 @@ export const uploadToS3 = async ( if (signedURLResponse.status !== 200) { if (onError) onError(signedURLResponse.statusText); - notification["error"]({ + notification.error({ message: i18n.t("documents.errors.getpresignurl", { message: signedURLResponse.statusText }) @@ -60,67 +70,76 @@ export const uploadToS3 = async ( //Key should be same as we provided to maintain backwards compatibility. const { presignedUrl: preSignedUploadUrlToS3, key: s3Key } = signedURLResponse.data.signedUrls[0]; - var options = { + const options = { onUploadProgress: (e) => { if (onProgress) onProgress({ percent: (e.loaded / e.total) * 100 }); } }; - const s3UploadResponse = await cleanAxios.put(preSignedUploadUrlToS3, file, options); - //Insert the document with the matching key. - let takenat; - if (fileType.includes("image")) { - try { - const exif = await exifr.parse(file); - takenat = exif && exif.DateTimeOriginal; - } catch (error) { - console.log("Unable to parse image file for EXIF Data", error.message); + try { + const s3UploadResponse = await cleanAxios.put(preSignedUploadUrlToS3, file, options); + //Insert the document with the matching key. + let takenat; + if (fileType.includes("image")) { + try { + const exif = await exifr.parse(file); + takenat = exif && exif.DateTimeOriginal; + } catch (error) { + console.log("Unable to parse image file for EXIF Data", error.message); + } } - } - const documentInsert = await client.mutate({ - mutation: INSERT_NEW_DOCUMENT, - variables: { - docInput: [ - { - ...(jobId ? { jobid: jobId } : {}), - ...(billId ? { billid: billId } : {}), - uploaded_by: uploaded_by, - key: s3Key, - type: fileType, - extension: s3UploadResponse.data.format || extension, - bodyshopid: bodyshop.id, - size: s3UploadResponse.data.bytes || file.size, //Leftover from Cloudinary. We don't do any optimization on upload, so it will always be file.size. - takenat - } - ] - } - }); - - if (!documentInsert.errors) { - if (onSuccess) - onSuccess({ - uid: documentInsert.data.insert_documents.returning[0].id, - name: documentInsert.data.insert_documents.returning[0].name, - status: "done", - key: documentInsert.data.insert_documents.returning[0].key - }); - notification.open({ - type: "success", - key: "docuploadsuccess", - message: i18n.t("documents.successes.insert") + const documentInsert = await client.mutate({ + mutation: INSERT_NEW_DOCUMENT, + variables: { + docInput: [ + { + ...(jobId ? { jobid: jobId } : {}), + ...(billId ? { billid: billId } : {}), + uploaded_by: uploaded_by, + key: s3Key, + type: fileType, + extension: s3UploadResponse.data.format || extension, + bodyshopid: bodyshop.id, + size: s3UploadResponse.data.bytes || file.size, //Leftover from Cloudinary. We don't do any optimization on upload, so it will always be file.size. + takenat + } + ] + } }); - if (callback) { - callback(); + + if (!documentInsert.errors) { + if (onSuccess) + onSuccess({ + uid: documentInsert.data.insert_documents.returning[0].id, + name: documentInsert.data.insert_documents.returning[0].name, + status: "done", + key: documentInsert.data.insert_documents.returning[0].key + }); + notification.success({ + key: "docuploadsuccess", + message: i18n.t("documents.successes.insert") + }); + if (callback) { + callback(); + } + } else { + if (onError) onError(JSON.stringify(documentInsert.errors)); + notification.error({ + message: i18n.t("documents.errors.insert", { + message: JSON.stringify(documentInsert.errors) + }) + }); + return; } - } else { - if (onError) onError(JSON.stringify(documentInsert.errors)); - notification["error"]({ + } catch (error) { + console.log("Error uploading file to S3", error.message, error.stack); + notification.error({ message: i18n.t("documents.errors.insert", { - message: JSON.stringify(documentInsert.errors) + message: error.message }) }); - return; + if (onError) onError(JSON.stringify(error.message)); } }; diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx index 4ce2d12a2..2fcd55a9f 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.reassign.component.jsx @@ -9,7 +9,7 @@ import { useNotification } from "../../contexts/Notifications/notificationContex import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries.js"; import { selectBodyshop } from "../../redux/user/user.selectors.js"; import JobSearchSelect from "../job-search-select/job-search-select.component.jsx"; - +import { isFunction } from "lodash"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); @@ -54,9 +54,8 @@ export function JobsDocumentsImgproxyGalleryReassign({ bodyshop, galleryImages, bodyshop.jobsizelimit - newJobData.data.documents_aggregate.aggregate.sum.size < transferedDocSizeTotal; if (shouldPreventTransfer) { - notification.open({ + notification.error({ key: "cannotuploaddocuments", - type: "error", message: t("documents.labels.reassign_limitexceeded_title"), description: t("documents.labels.reassign_limitexceeded") }); @@ -81,17 +80,17 @@ export function JobsDocumentsImgproxyGalleryReassign({ bodyshop, galleryImages, }) }); //Add in confirmation & errors. - if (callback) callback(); + if (isFunction(callback)) callback(); if (res.errors) { - notification["error"]({ + notification.error({ message: t("documents.errors.updating", { message: JSON.stringify(res.errors) }) }); } if (!res.mutationResult?.errors) { - notification["success"]({ + notification.success({ message: t("documents.successes.updated") }); } diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx index 156362ff1..61b009f13 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx @@ -17,6 +17,7 @@ import JobsDocumentsGalleryReassign from "./jobs-document-imgproxy-gallery.reass import JobsDocumentsDeleteButton from "./jobs-documents-imgproxy-gallery.delete.component"; import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-imgproxy-gallery.selectall.component"; import i18n from "i18next"; +import { isFunction } from "lodash"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -40,19 +41,19 @@ function JobsDocumentsImgproxyComponent({ downloadIdentifier, ignoreSizeLimit }) { - const [galleryImages, setgalleryImages] = useState({ images: [], other: [] }); + const [galleryImages, setGalleryImages] = useState({ images: [], other: [] }); const { t } = useTranslation(); const [modalState, setModalState] = useState({ open: false, index: 0 }); const fetchThumbnails = () => { - fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId }); + fetchImgproxyThumbnails({ setStateCallback: setGalleryImages, jobId }); }; useEffect(() => { if (data) { fetchThumbnails(); } - }, [data, setgalleryImages]); + }, [data]); const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); @@ -65,7 +66,7 @@ function JobsDocumentsImgproxyComponent({ onClick={() => { //Handle any doc refresh. - refetch && refetch(); + isFunction(refetch) && refetch(); //Do the imgproxy refresh too fetchThumbnails(); @@ -73,7 +74,7 @@ function JobsDocumentsImgproxyComponent({ > - + { - setgalleryImages({ + setGalleryImages({ ...galleryImages, images: galleryImages.images.map((g, idx) => index === idx ? { ...g, isSelected: !g.isSelected } : g @@ -148,7 +149,7 @@ function JobsDocumentsImgproxyComponent({ window.open(galleryImages.other[index].source, "_blank", "toolbar=0,location=0,menubar=0"); }} onSelect={(index) => { - setgalleryImages({ + setGalleryImages({ ...galleryImages, other: galleryImages.other.map((g, idx) => (index === idx ? { ...g, isSelected: !g.isSelected } : g)) }); @@ -160,6 +161,7 @@ function JobsDocumentsImgproxyComponent({ { const newWindow = window.open( `${window.location.protocol}//${window.location.host}/edit?documentId=${ @@ -202,7 +204,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgprox export const fetchImgproxyThumbnails = async ({ setStateCallback, jobId, imagesOnly }) => { const result = await axios.post("/media/imgproxy/thumbnails", { jobid: jobId }); - let documents = result.data.reduce( + const documents = result.data.reduce( (acc, value) => { if (value.type.startsWith("image")) { acc.images.push({ @@ -259,9 +261,6 @@ export const fetchImgproxyThumbnails = async ({ setStateCallback, jobId, imagesO }, { images: [], other: [] } ); - if (imagesOnly) { - setStateCallback(documents.images); - } else { - setStateCallback(documents); - } + + setStateCallback(imagesOnly ? documents.images : documents); }; diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx index 72143531b..4701bca67 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.delete.component.jsx @@ -5,7 +5,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { logImEXEvent } from "../../firebase/firebase.utils.js"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; - +import { isFunction } from "lodash"; /* ################################################################################################ @@ -34,22 +34,21 @@ export default function JobsDocumentsImgproxyDeleteButton({ galleryImages, delet }); if (res.data.error) { - notification["error"]({ + notification.error({ message: t("documents.errors.deleting", { error: JSON.stringify(res.data.error.response.errors) }) }); } else { - notification.open({ + notification.success({ key: "docdeletedsuccesfully", - type: "success", message: t("documents.successes.delete") }); - if (deletionCallback) deletionCallback(); + if (isFunction(deletionCallback)) deletionCallback(); } } catch (error) { - notification["error"]({ + notification.error({ message: t("documents.errors.deleting", { error: error.message }) diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 633a8a3c6..7d52542e5 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2253,7 +2253,7 @@ exports.UPDATE_PARTS_CRITICAL = `mutation UPDATE_PARTS_CRITICAL ($IdsToMarkCriti notcritical: update_joblines(where: {id: {_nin: $IdsToMarkCritical}, jobid: {_eq: $jobid}}, _set: {critical: false}) { affected_rows } -}`;; +}`; exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) { associations(where: {active: {_eq: true}, useremail: {_eq: $user}}) { @@ -2706,8 +2706,6 @@ exports.INSERT_AUDIT_TRAIL = ` } `; - - exports.GET_DOCUMENTS_BY_JOB = ` query GET_DOCUMENTS_BY_JOB($jobId: uuid!) { jobs_by_pk(id: $jobId) { From 787366b23114054e64a5e00c01ec3c6172a8b6b9 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 28 Feb 2025 09:32:16 -0800 Subject: [PATCH 10/33] IO-3092 Refactor exports. --- server/media/media.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/server/media/media.js b/server/media/media.js index 6706d3392..06b1c9bb8 100644 --- a/server/media/media.js +++ b/server/media/media.js @@ -11,12 +11,14 @@ require("dotenv").config({ var cloudinary = require("cloudinary").v2; cloudinary.config(process.env.CLOUDINARY_URL); -exports.createSignedUploadURL = (req, res) => { +const createSignedUploadURL = (req, res) => { logger.log("media-signed-upload", "DEBUG", req.user.email, null, null); res.send(cloudinary.utils.api_sign_request(req.body, process.env.CLOUDINARY_API_SECRET)); }; -exports.downloadFiles = (req, res) => { +exports.createSignedUploadURL = createSignedUploadURL; + +const downloadFiles = (req, res) => { const { ids } = req.body; logger.log("media-bulk-download", "DEBUG", req.user.email, ids, null); @@ -26,8 +28,9 @@ exports.downloadFiles = (req, res) => { }); res.send(url); }; +exports.downloadFiles = downloadFiles; -exports.deleteFiles = async (req, res) => { +const deleteFiles = async (req, res) => { const { ids } = req.body; const types = _.groupBy(ids, (x) => DetermineFileType(x.type)); @@ -88,7 +91,9 @@ exports.deleteFiles = async (req, res) => { } }; -exports.renameKeys = async (req, res) => { +exports.deleteFiles = deleteFiles; + +const renameKeys = async (req, res) => { const { documents, tojobid } = req.body; logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents); @@ -146,6 +151,7 @@ exports.renameKeys = async (req, res) => { res.json({ errors: "No images were succesfully moved on remote server. " }); } }; +exports.renameKeys = renameKeys; //Also needs to be updated in upload utility and mobile app. function DetermineFileType(filetype) { From 0810798d3019afba5591d334c8d685d494c4495c Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 4 Mar 2025 11:14:43 -0800 Subject: [PATCH 11/33] IO-3092 Remove compiler warnings. --- .../documents-upload-imgproxy.utility.js | 1 - .../jobs-documents-imgproxy-gallery.component.jsx | 12 ++++++------ ...documents-imgproxy-gallery.external.component.jsx | 10 ++-------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js index 8fa7cb001..7ecbd50bb 100644 --- a/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js +++ b/client/src/components/documents-upload-imgproxy/documents-upload-imgproxy.utility.js @@ -5,7 +5,6 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; import { axiosAuthInterceptorId } from "../../utils/CleanAxios"; import client from "../../utils/GraphQLClient"; -import { error } from "logrocket"; //Context: currentUserEmail, bodyshop, jobid, invoiceid diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx index 61b009f13..a07ed0bf1 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.component.jsx @@ -1,7 +1,9 @@ import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons"; import { Button, Card, Col, Row, Space } from "antd"; import axios from "axios"; -import { useEffect, useState } from "react"; +import i18n from "i18next"; +import { isFunction } from "lodash"; +import { useCallback, useEffect, useState } from "react"; import { Gallery } from "react-grid-gallery"; import { useTranslation } from "react-i18next"; import Lightbox from "react-image-lightbox"; @@ -16,8 +18,6 @@ import JobsDocumentsDownloadButton from "./jobs-document-imgproxy-gallery.downlo import JobsDocumentsGalleryReassign from "./jobs-document-imgproxy-gallery.reassign.component"; import JobsDocumentsDeleteButton from "./jobs-documents-imgproxy-gallery.delete.component"; import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-imgproxy-gallery.selectall.component"; -import i18n from "i18next"; -import { isFunction } from "lodash"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -45,15 +45,15 @@ function JobsDocumentsImgproxyComponent({ const { t } = useTranslation(); const [modalState, setModalState] = useState({ open: false, index: 0 }); - const fetchThumbnails = () => { + const fetchThumbnails = useCallback(() => { fetchImgproxyThumbnails({ setStateCallback: setGalleryImages, jobId }); - }; + }, [jobId, setGalleryImages]); useEffect(() => { if (data) { fetchThumbnails(); } - }, [data]); + }, [data, fetchThumbnails]); const hasMediaAccess = HasFeatureAccess({ bodyshop, featureName: "media" }); const hasMobileAccess = HasFeatureAccess({ bodyshop, featureName: "mobile" }); diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx index 75943dceb..a970b01b7 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component.jsx @@ -1,6 +1,5 @@ import { useEffect } from "react"; import { Gallery } from "react-grid-gallery"; -import { useTranslation } from "react-i18next"; import { fetchImgproxyThumbnails } from "./jobs-documents-imgproxy-gallery.component"; /* @@ -13,15 +12,10 @@ import { fetchImgproxyThumbnails } from "./jobs-documents-imgproxy-gallery.compo function JobsDocumentImgproxyGalleryExternal({ jobId, externalMediaState }) { const [galleryImages, setgalleryImages] = externalMediaState; - const { t } = useTranslation(); - - const fetchThumbnails = () => { - fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId, imagesOnly: true }); - }; useEffect(() => { - fetchThumbnails(); - }, [jobId]); + if (jobId) fetchImgproxyThumbnails({ setStateCallback: setgalleryImages, jobId, imagesOnly: true }); + }, [jobId, setgalleryImages]); return (
From 4a9b0cae698c4d8f3fa3545463676107b08bc0e1 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 14 Mar 2025 21:47:03 -0700 Subject: [PATCH 12/33] IO-2999 IO Test Report Server Migration Signed-off-by: Allan Carr --- client/.env.development.imex | 2 +- client/.env.development.rome | 2 +- client/.env.test.imex | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/.env.development.imex b/client/.env.development.imex index 968dbab37..ef94b0621 100644 --- a/client/.env.development.imex +++ b/client/.env.development.imex @@ -9,6 +9,6 @@ VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4' VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g VITE_APP_AXIOS_BASE_API_URL=/api/ -VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online +VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc VITE_APP_INSTANCE=IMEX diff --git a/client/.env.development.rome b/client/.env.development.rome index e0805f439..5d0c366fa 100644 --- a/client/.env.development.rome +++ b/client/.env.development.rome @@ -10,7 +10,7 @@ VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo' VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g VITE_APP_AXIOS_BASE_API_URL=/api/ -VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online +VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc VITE_APP_COUNTRY=USA VITE_APP_INSTANCE=ROME diff --git a/client/.env.test.imex b/client/.env.test.imex index a8a359f46..84167c4cb 100644 --- a/client/.env.test.imex +++ b/client/.env.test.imex @@ -9,7 +9,7 @@ VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250 VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo' VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g VITE_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/ -VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online +VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online VITE_APP_IS_TEST=true VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc VITE_APP_INSTANCE=IMEX From b9eb62220758069540fc8703b2aeed0aad27cadb Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 21 Mar 2025 13:16:57 -0400 Subject: [PATCH 13/33] IO-3181-Testing-Framework-Selection: Package update checkpoint --- client/package-lock.json | 383 ++++++++++++++++++++++++++++----------- client/package.json | 28 +-- package-lock.json | 235 +++++++++++------------- package.json | 26 +-- 4 files changed, 409 insertions(+), 263 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index fbd87949f..c49b5dbd4 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,22 +10,22 @@ "hasInstallScript": true, "dependencies": { "@ant-design/pro-layout": "^7.22.3", - "@apollo/client": "^3.13.1", + "@apollo/client": "^3.13.5", "@emotion/is-prop-valid": "^1.3.1", "@fingerprintjs/fingerprintjs": "^4.6.1", "@jsreport/browser-client": "^3.1.0", - "@reduxjs/toolkit": "^2.6.0", - "@sentry/cli": "^2.42.2", - "@sentry/react": "^9.3.0", + "@reduxjs/toolkit": "^2.6.1", + "@sentry/cli": "^2.42.4", + "@sentry/react": "^9.7.0", "@sentry/vite-plugin": "^3.2.2", "@splitsoftware/splitio-react": "^1.13.0", "@tanem/react-nprogress": "^5.0.53", "@vitejs/plugin-react": "^4.3.4", - "antd": "^5.24.2", + "antd": "^5.24.4", "apollo-link-logger": "^2.0.1", "apollo-link-sentry": "^4.1.0", "autosize": "^6.0.1", - "axios": "^1.8.1", + "axios": "^1.8.4", "classnames": "^2.5.1", "css-box-model": "^1.2.1", "dayjs": "^1.11.13", @@ -39,9 +39,9 @@ "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.4", "immutability-helper": "^3.1.1", - "libphonenumber-js": "^1.12.4", + "libphonenumber-js": "^1.12.6", "logrocket": "^8.1.2", - "markerjs2": "^2.32.3", + "markerjs2": "^2.32.4", "memoize-one": "^6.0.0", "normalize-url": "^8.0.1", "object-hash": "^3.0.0", @@ -75,12 +75,12 @@ "redux-saga": "^1.3.0", "redux-state-sync": "^3.1.4", "reselect": "^5.1.1", - "sass": "^1.85.1", + "sass": "^1.86.0", "socket.io-client": "^4.8.1", - "styled-components": "^6.1.15", + "styled-components": "^6.1.16", "subscriptions-transport-ws": "^0.11.0", "use-memo-one": "^1.1.3", - "userpilot": "^1.3.8", + "userpilot": "^1.3.9", "vite-plugin-ejs": "^1.7.0", "web-vitals": "^3.5.2" }, @@ -88,10 +88,10 @@ "@ant-design/icons": "^5.6.1", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.26.3", - "@dotenvx/dotenvx": "^1.38.3", + "@dotenvx/dotenvx": "^1.39.0", "@emotion/babel-plugin": "^11.13.5", "@emotion/react": "^11.14.0", - "@eslint/js": "^9.21.0", + "@eslint/js": "^9.22.0", "@sentry/webpack-plugin": "^3.2.2", "@testing-library/cypress": "^10.0.2", "browserslist": "^4.24.4", @@ -109,7 +109,7 @@ "react-error-overlay": "^6.1.0", "redux-logger": "^3.0.6", "source-map-explorer": "^2.5.3", - "vite": "^6.2.0", + "vite": "^6.2.2", "vite-plugin-babel": "^1.3.0", "vite-plugin-eslint": "^1.8.1", "vite-plugin-node-polyfills": "^0.23.0", @@ -323,9 +323,9 @@ } }, "node_modules/@apollo/client": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.1.tgz", - "integrity": "sha512-HaAt62h3jNUXpJ1v5HNgUiCzPP1c5zc2Q/FeTb2cTk/v09YlhoqKKHQFJI7St50VCJ5q8JVIc03I5bRcBrQxsg==", + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.13.5.tgz", + "integrity": "sha512-ceHa1lApLAiGmUur4V+G/CrjwVwHYujfB7U5HM++poCgHpfGn6eet8YGM93fgeWjYX85SaqwdZbQk18IVwhRHg==", "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", @@ -2426,9 +2426,9 @@ } }, "node_modules/@dotenvx/dotenvx": { - "version": "1.38.3", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.38.3.tgz", - "integrity": "sha512-6tquYDfAiJbgQbYwWfL0jJHiUumY5EiFXVswk9sTwn5lWICMwOPmMOrM9TEVLzesfNMYwDyUiMp5WAA6yXs+SQ==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.39.0.tgz", + "integrity": "sha512-qGfDpL/3S17MQYXpR3HkBS5xNQ7wiFlqLdpr+iIQzv17aMRcSlgL4EjMIsYFZ540Dq17J+y5FVElA1AkVoXiUA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3159,9 +3159,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", - "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", + "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", "dev": true, "license": "MIT", "engines": { @@ -4669,9 +4669,9 @@ "license": "MIT" }, "node_modules/@reduxjs/toolkit": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz", - "integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.1.tgz", + "integrity": "sha512-SSlIqZNYhqm/oMkXbtofwZSt9lrncblzo6YcZ9zoX+zLngRBrCOjK4lNLdkNucJF58RHOWrD9txT3bT3piH7Zw==", "license": "MIT", "dependencies": { "immer": "^10.0.3", @@ -5261,50 +5261,50 @@ "license": "MIT" }, "node_modules/@sentry-internal/browser-utils": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.3.0.tgz", - "integrity": "sha512-G3z4HCUyb5nJe03EPUhWjnaHqMDt4mOTFJDNha3DGoB51lMYojpQI1Qo1u6bY4qkWVSO1c+HqOU0RVsXoAchtQ==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.7.0.tgz", + "integrity": "sha512-1wVd8mCvbeGs3wSDZFYQ9RE8I8Ii8iQ2wnHKbONk645RIT6FwSCPHlnFKWYSFVxo85Wm6SFbKcv1zvTNXUVhfQ==", "license": "MIT", "dependencies": { - "@sentry/core": "9.3.0" + "@sentry/core": "9.7.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/feedback": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.3.0.tgz", - "integrity": "sha512-LQmIbQaATlN5QEwCD2Xt+7VKfwfR5W3dbn0jdF1x4hQFE/srdnOj60xMz/mj3tP5BxV552xJniGsyZ8lXHDb2A==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.7.0.tgz", + "integrity": "sha512-Ld51wOfXYO90++ZGBuVF/5PAMBnVWFfvvleXRgA9VaL2hq296+tLXYVccHTgH+dZmwjJgsybDAxHdj+k1FFnmw==", "license": "MIT", "dependencies": { - "@sentry/core": "9.3.0" + "@sentry/core": "9.7.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.3.0.tgz", - "integrity": "sha512-ZkH+Gahn89JygpuiFn26ZgAqJXHtnr+HjfQ2ONOFoWQHNH6X5wk75UTma55aYk1d8VcBPFoU6WjFhZoQ55SV1g==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.7.0.tgz", + "integrity": "sha512-JOwwqe3JtcbkvC/hM7qr1x40lk3K5zm8r2HHVKVLzrf6cAuLF9x17R7h+0cDOHcilacbtIQQ9Fjjb63a1/iDLQ==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.3.0", - "@sentry/core": "9.3.0" + "@sentry-internal/browser-utils": "9.7.0", + "@sentry/core": "9.7.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.3.0.tgz", - "integrity": "sha512-MhDMJeRGa55a0D541+OzTFMWwbabthhDGbAL90/NpappfyeBbAiktmCNl0BFTZuRbCGrC2m1LLCqHegCVKW4fQ==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.7.0.tgz", + "integrity": "sha512-E/XESnVLV+CUyrucrxK2kHpEKTDrz7LTBwjnt3FDHA8MfwMRX+np0sYjvcIRWAG4xxBOYBGlWD2+n0Y2pqYa2Q==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "9.3.0", - "@sentry/core": "9.3.0" + "@sentry-internal/replay": "9.7.0", + "@sentry/core": "9.7.0" }, "engines": { "node": ">=18" @@ -5320,16 +5320,16 @@ } }, "node_modules/@sentry/browser": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.3.0.tgz", - "integrity": "sha512-yPwWWQo/hpN63p0NGmk/Dd1Fx5CQRWNMfuV7dtfPBtg3vRjDecA9OLyK29AqK5h3Fl8FuJOyOqB87CvtXUqh5g==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.7.0.tgz", + "integrity": "sha512-G2AZuWAfatv30rFWYcbAZNnty9826ld8k+EucYLtDGN0uBdHXX9XT/x7/C8dprtV16jGWzrfvMza8C7Z4uj8BA==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.3.0", - "@sentry-internal/feedback": "9.3.0", - "@sentry-internal/replay": "9.3.0", - "@sentry-internal/replay-canvas": "9.3.0", - "@sentry/core": "9.3.0" + "@sentry-internal/browser-utils": "9.7.0", + "@sentry-internal/feedback": "9.7.0", + "@sentry-internal/replay": "9.7.0", + "@sentry-internal/replay-canvas": "9.7.0", + "@sentry/core": "9.7.0" }, "engines": { "node": ">=18" @@ -5354,7 +5354,7 @@ "node": ">= 14" } }, - "node_modules/@sentry/cli": { + "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli": { "version": "2.42.2", "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.42.2.tgz", "integrity": "sha512-spb7S/RUumCGyiSTg8DlrCX4bivCNmU/A1hcfkwuciTFGu8l5CDc2I6jJWWZw8/0enDGxuj5XujgXvU5tr4bxg==", @@ -5383,7 +5383,7 @@ "@sentry/cli-win32-x64": "2.42.2" } }, - "node_modules/@sentry/cli-darwin": { + "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-darwin": { "version": "2.42.2", "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.42.2.tgz", "integrity": "sha512-GtJSuxER7Vrp1IpxdUyRZzcckzMnb4N5KTW7sbTwUiwqARRo+wxS+gczYrS8tdgtmXs5XYhzhs+t4d52ITHMIg==", @@ -5396,7 +5396,7 @@ "node": ">=10" } }, - "node_modules/@sentry/cli-linux-arm": { + "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-linux-arm": { "version": "2.42.2", "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.2.tgz", "integrity": "sha512-7udCw+YL9lwq+9eL3WLspvnuG+k5Icg92YE7zsteTzWLwgPVzaxeZD2f8hwhsu+wmL+jNqbpCRmktPteh3i2mg==", @@ -5413,7 +5413,7 @@ "node": ">=10" } }, - "node_modules/@sentry/cli-linux-arm64": { + "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-linux-arm64": { "version": "2.42.2", "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.2.tgz", "integrity": "sha512-BOxzI7sgEU5Dhq3o4SblFXdE9zScpz6EXc5Zwr1UDZvzgXZGosUtKVc7d1LmkrHP8Q2o18HcDWtF3WvJRb5Zpw==", @@ -5430,7 +5430,7 @@ "node": ">=10" } }, - "node_modules/@sentry/cli-linux-i686": { + "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-linux-i686": { "version": "2.42.2", "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.2.tgz", "integrity": "sha512-Sw/dQp5ZPvKnq3/y7wIJyxTUJYPGoTX/YeMbDs8BzDlu9to2LWV3K3r7hE7W1Lpbaw4tSquUHiQjP5QHCOS7aQ==", @@ -5448,7 +5448,7 @@ "node": ">=10" } }, - "node_modules/@sentry/cli-linux-x64": { + "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-linux-x64": { "version": "2.42.2", "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.2.tgz", "integrity": "sha512-mU4zUspAal6TIwlNLBV5oq6yYqiENnCWSxtSQVzWs0Jyq97wtqGNG9U+QrnwjJZ+ta/hvye9fvL2X25D/RxHQw==", @@ -5465,7 +5465,7 @@ "node": ">=10" } }, - "node_modules/@sentry/cli-win32-i686": { + "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-win32-i686": { "version": "2.42.2", "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.2.tgz", "integrity": "sha512-iHvFHPGqgJMNqXJoQpqttfsv2GI3cGodeTq4aoVLU/BT3+hXzbV0x1VpvvEhncJkDgDicJpFLM8sEPHb3b8abw==", @@ -5482,7 +5482,7 @@ "node": ">=10" } }, - "node_modules/@sentry/cli-win32-x64": { + "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-win32-x64": { "version": "2.42.2", "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.2.tgz", "integrity": "sha512-vPPGHjYoaGmfrU7xhfFxG7qlTBacroz5NdT+0FmDn6692D8IvpNXl1K+eV3Kag44ipJBBeR8g1HRJyx/F/9ACw==", @@ -5498,6 +5498,171 @@ "node": ">=10" } }, + "node_modules/@sentry/bundler-plugin-core/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/@sentry/bundler-plugin-core/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sentry/cli": { + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.42.4.tgz", + "integrity": "sha512-BoSZDAWJiz/40tu6LuMDkSgwk4xTsq6zwqYoUqLU3vKBR/VsaaQGvu6EWxZXORthfZU2/5Agz0+t220cge6VQw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@sentry/cli-darwin": "2.42.4", + "@sentry/cli-linux-arm": "2.42.4", + "@sentry/cli-linux-arm64": "2.42.4", + "@sentry/cli-linux-i686": "2.42.4", + "@sentry/cli-linux-x64": "2.42.4", + "@sentry/cli-win32-i686": "2.42.4", + "@sentry/cli-win32-x64": "2.42.4" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.42.4.tgz", + "integrity": "sha512-PZV4Y97VDWBR4rIt0HkJfXaBXlebIN2s/FDzC3iHINZE5OG62CDFsnC4/lbGlf2/UZLDaGGIK7mYwSHhTvN+HQ==", + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm": { + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.4.tgz", + "integrity": "sha512-lBn0oeeg62h68/4Eo6zbPq99Idz5t0VRV48rEU/WKeM4MtQCvG/iGGQ3lBFW2yNiUBzXZIK9poXLEcgbwmcRVw==", + "cpu": [ + "arm" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.4.tgz", + "integrity": "sha512-Ex8vRnryyzC/9e43daEmEqPS+9uirY/l6Hw2lAvhBblFaL7PTWNx52H+8GnYGd9Zy2H3rWNyBDYfHwnErg38zA==", + "cpu": [ + "arm64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-i686": { + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.4.tgz", + "integrity": "sha512-IBJg0aHjsLCL4LvcFa3cXIjA+4t5kPqBT9y+PoDu4goIFxYD8zl7mbUdGJutvJafTk8Akf4ss4JJXQBjg019zA==", + "cpu": [ + "x86", + "ia32" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-x64": { + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.4.tgz", + "integrity": "sha512-gXI5OEiOSNiAEz7VCE6AZcAgHJ47mlgal3+NmbE8XcHmFOnyDws9FNie6PJAy8KZjXi3nqoBP9JVAbnmOix3uA==", + "cpu": [ + "x64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-i686": { + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.4.tgz", + "integrity": "sha512-vZuR3UPHKqOMniyrijrrsNwn9usaRysXq78F6WV0cL0ZyPLAmY+KBnTDSFk1Oig2pURnzaTm+RtcZu2fc8mlzg==", + "cpu": [ + "x86", + "ia32" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-x64": { + "version": "2.42.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.4.tgz", + "integrity": "sha512-OIBj3uaQ6nAERSm5Dcf8UIhyElEEwMNsZEEppQpN4IKl0mrwb/57AznM23Dvpu6GR8WGbVQUSolt879YZR5E9g==", + "cpu": [ + "x64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@sentry/cli/node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5520,22 +5685,22 @@ } }, "node_modules/@sentry/core": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.3.0.tgz", - "integrity": "sha512-SxQ4z7wTkfguvYb2ctNEMU9kVAbhl9ymfjhLnrvtygTwL5soLqAKdco/lX/4P9K9Osgb2Dl6urQWRl+AhzKVbQ==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.7.0.tgz", + "integrity": "sha512-EprjtU7F6eltB4Nx8fzWFXsfAC/6yNGuKo2bHKeIAmNufjD0X4ifz+iB3d0pKuwsn9jQbLrQTIGwKdTO3dstFw==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@sentry/react": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.3.0.tgz", - "integrity": "sha512-/ruDHBHLDXmZoEHNCSjdekZr9+0pbOC5+BY1oABGoDXRISGyoenOBtAsX8TsaC9oJYhr16yKDFlYxzzQRhxDyg==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.7.0.tgz", + "integrity": "sha512-qD3Jb1/fvofdw5+aC/ARJNu1lVO6Ndoma15V+jjKH+rBaYAXftkboUvhTzvpAM8o1tw3jbioJDQlmIHehUzjzg==", "license": "MIT", "dependencies": { - "@sentry/browser": "9.3.0", - "@sentry/core": "9.3.0", + "@sentry/browser": "9.7.0", + "@sentry/core": "9.7.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -6559,9 +6724,9 @@ } }, "node_modules/antd": { - "version": "5.24.2", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.24.2.tgz", - "integrity": "sha512-7Z9HsE3ZIK3sE/WuUqii3w7Gl1IJuRL21sDUTtkN95JS5KhRYP8ISv7m/HxsJ3Mn/yxgojBCgLPJ212+Dn+aPw==", + "version": "5.24.4", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.24.4.tgz", + "integrity": "sha512-s89666DcoWeekJFaIqbtz2vRlIvgPR28GuDYYGUpW1mVP08bV7HZAPBH5lFJKYNGKrN3dHbZGgRK5aNRD2iPHg==", "license": "MIT", "dependencies": { "@ant-design/colors": "^7.2.0", @@ -6579,7 +6744,7 @@ "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.11", - "rc-cascader": "~3.33.0", + "rc-cascader": "~3.33.1", "rc-checkbox": "~3.5.0", "rc-collapse": "~3.9.0", "rc-dialog": "~9.6.0", @@ -6587,14 +6752,14 @@ "rc-dropdown": "~4.2.1", "rc-field-form": "~2.7.0", "rc-image": "~7.11.0", - "rc-input": "~1.7.2", + "rc-input": "~1.7.3", "rc-input-number": "~9.4.0", "rc-mentions": "~2.19.1", "rc-menu": "~9.16.1", "rc-motion": "^2.9.5", "rc-notification": "~5.6.3", "rc-pagination": "~5.1.0", - "rc-picker": "~4.11.2", + "rc-picker": "~4.11.3", "rc-progress": "~4.0.0", "rc-rate": "~2.13.1", "rc-resize-observer": "^1.4.3", @@ -6603,11 +6768,11 @@ "rc-slider": "~11.1.8", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.50.3", + "rc-table": "~7.50.4", "rc-tabs": "~15.5.1", "rc-textarea": "~1.9.0", "rc-tooltip": "~6.4.0", - "rc-tree": "~5.13.0", + "rc-tree": "~5.13.1", "rc-tree-select": "~5.27.0", "rc-upload": "~4.8.1", "rc-util": "^5.44.4", @@ -7037,9 +7202,9 @@ } }, "node_modules/axios": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", - "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -12486,9 +12651,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.4.tgz", - "integrity": "sha512-vLmhg7Gan7idyAKfc6pvCtNzvar4/eIzrVVk3hjNFH5+fGqyjD0gQRovdTrDl20wsmZhBtmZpcsR0tOfquwb8g==", + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz", + "integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==", "license": "MIT" }, "node_modules/lines-and-columns": { @@ -12844,9 +13009,9 @@ } }, "node_modules/markerjs2": { - "version": "2.32.3", - "resolved": "https://registry.npmjs.org/markerjs2/-/markerjs2-2.32.3.tgz", - "integrity": "sha512-D7oD4BT5NOsQbugdcO2TFmcw9ZMHp96Ih09A5f0UndxiQNWuz+j5zymtkTHs0WU+oOR8K6dyTufv4KtfJ6diBg==", + "version": "2.32.4", + "resolved": "https://registry.npmjs.org/markerjs2/-/markerjs2-2.32.4.tgz", + "integrity": "sha512-pk8gZMqSw0iDwSuH4Rt3jsYwA2J0EYUngIFIUvkHFVTiZPK+djuwrv4wfdK81I81FqnQ5iYp9buv/Sjg3Td0Tw==", "license": "SEE LICENSE IN LICENSE" }, "node_modules/material-colors": { @@ -14775,9 +14940,9 @@ } }, "node_modules/rc-cascader": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.33.0.tgz", - "integrity": "sha512-JvZrMbKBXIbEDmpIORxqvedY/bck6hGbs3hxdWT8eS9wSQ1P7//lGxbyKjOSyQiVBbgzNWriSe6HoMcZO/+0rQ==", + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.33.1.tgz", + "integrity": "sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", @@ -14909,9 +15074,9 @@ } }, "node_modules/rc-input": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.7.2.tgz", - "integrity": "sha512-g3nYONnl4edWj2FfVoxsU3Ec4XTE+Hb39Kfh2MFxMZjp/0gGyPUgy/v7ZhS27ZxUFNkuIDYXm9PJsLyJbtg86A==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.7.3.tgz", + "integrity": "sha512-A5w4egJq8+4JzlQ55FfQjDnPvOaAbzwC3VLOAdOytyek3TboSOP9qxN+Gifup+shVXfvecBLBbWBpWxmk02SWQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -15220,9 +15385,9 @@ } }, "node_modules/rc-table": { - "version": "7.50.3", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.3.tgz", - "integrity": "sha512-Z4/zNCzjv7f/XzPRecb+vJU0DJKdsYt4YRkDzNl4G05m7JmxrKGYC2KqN1Ew6jw2zJq7cxVv3z39qyZOHMuf7A==", + "version": "7.50.4", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.4.tgz", + "integrity": "sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -15296,9 +15461,9 @@ } }, "node_modules/rc-tree": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.0.tgz", - "integrity": "sha512-2+lFvoVRnvHQ1trlpXMOWtF8BUgF+3TiipG72uOfhpL5CUdXCk931kvDdUkTL/IZVtNEDQKwEEmJbAYJSA5NnA==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -16533,9 +16698,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.85.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz", - "integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==", + "version": "1.86.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.0.tgz", + "integrity": "sha512-zV8vGUld/+mP4KbMLJMX7TyGCuUp7hnkOScgCMsWuHtns8CWBoz+vmEhoGMXsaJrbUP8gj+F1dLvVe79sK8UdA==", "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -17447,9 +17612,9 @@ } }, "node_modules/styled-components": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.15.tgz", - "integrity": "sha512-PpOTEztW87Ua2xbmLa7yssjNyUF9vE7wdldRfn1I2E6RTkqknkBYpj771OxM/xrvRGinLy2oysa7GOd7NcZZIA==", + "version": "6.1.16", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.16.tgz", + "integrity": "sha512-KpWB6ORAWGmbWM10cDJfEV6sXc/uVkkkQV3SLwTNQ/E/PqWgNHIoMSLh1Lnk2FkB9+JHK7uuMq1i+9ArxDD7iQ==", "license": "MIT", "dependencies": { "@emotion/is-prop-valid": "1.2.2", @@ -18578,9 +18743,9 @@ } }, "node_modules/userpilot": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/userpilot/-/userpilot-1.3.8.tgz", - "integrity": "sha512-Hoym2S7j5IvGzb3n/eOwX3FE5PgzMjk5148uU1WTNM5iw784u6+LZiu3DC7NuovVrwYzI99qy5Ossqmft9c74A==", + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/userpilot/-/userpilot-1.3.9.tgz", + "integrity": "sha512-V0QIuIlAJPB8s3j+qtv7BW7NKSXthlZWuowIu+IZOMGLgUbqQTaSW5m1Ct4wJviPKUNOi8kbhCXN4c4b3zcJzg==", "license": "MIT", "dependencies": { "@ndhoule/includes": "^2.0.1", @@ -18691,9 +18856,9 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz", + "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/client/package.json b/client/package.json index e54a6a098..37a326023 100644 --- a/client/package.json +++ b/client/package.json @@ -9,22 +9,22 @@ "proxy": "http://localhost:4000", "dependencies": { "@ant-design/pro-layout": "^7.22.3", - "@apollo/client": "^3.13.1", + "@apollo/client": "^3.13.5", "@emotion/is-prop-valid": "^1.3.1", "@fingerprintjs/fingerprintjs": "^4.6.1", "@jsreport/browser-client": "^3.1.0", - "@reduxjs/toolkit": "^2.6.0", - "@sentry/cli": "^2.42.2", - "@sentry/react": "^9.3.0", + "@reduxjs/toolkit": "^2.6.1", + "@sentry/cli": "^2.42.4", + "@sentry/react": "^9.7.0", "@sentry/vite-plugin": "^3.2.2", "@splitsoftware/splitio-react": "^1.13.0", "@tanem/react-nprogress": "^5.0.53", "@vitejs/plugin-react": "^4.3.4", - "antd": "^5.24.2", + "antd": "^5.24.4", "apollo-link-logger": "^2.0.1", "apollo-link-sentry": "^4.1.0", "autosize": "^6.0.1", - "axios": "^1.8.1", + "axios": "^1.8.4", "classnames": "^2.5.1", "css-box-model": "^1.2.1", "dayjs": "^1.11.13", @@ -38,9 +38,9 @@ "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.4", "immutability-helper": "^3.1.1", - "libphonenumber-js": "^1.12.4", + "libphonenumber-js": "^1.12.6", "logrocket": "^8.1.2", - "markerjs2": "^2.32.3", + "markerjs2": "^2.32.4", "memoize-one": "^6.0.0", "normalize-url": "^8.0.1", "object-hash": "^3.0.0", @@ -74,12 +74,12 @@ "redux-saga": "^1.3.0", "redux-state-sync": "^3.1.4", "reselect": "^5.1.1", - "sass": "^1.85.1", + "sass": "^1.86.0", "socket.io-client": "^4.8.1", - "styled-components": "^6.1.15", + "styled-components": "^6.1.16", "subscriptions-transport-ws": "^0.11.0", "use-memo-one": "^1.1.3", - "userpilot": "^1.3.8", + "userpilot": "^1.3.9", "vite-plugin-ejs": "^1.7.0", "web-vitals": "^3.5.2" }, @@ -123,10 +123,10 @@ "@ant-design/icons": "^5.6.1", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.26.3", - "@dotenvx/dotenvx": "^1.38.3", + "@dotenvx/dotenvx": "^1.39.0", "@emotion/babel-plugin": "^11.13.5", "@emotion/react": "^11.14.0", - "@eslint/js": "^9.21.0", + "@eslint/js": "^9.22.0", "@sentry/webpack-plugin": "^3.2.2", "@testing-library/cypress": "^10.0.2", "browserslist": "^4.24.4", @@ -144,7 +144,7 @@ "react-error-overlay": "^6.1.0", "redux-logger": "^3.0.6", "source-map-explorer": "^2.5.3", - "vite": "^6.2.0", + "vite": "^6.2.2", "vite-plugin-babel": "^1.3.0", "vite-plugin-eslint": "^1.8.1", "vite-plugin-node-polyfills": "^0.23.0", diff --git a/package-lock.json b/package-lock.json index ee26aab5c..484fa6d84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "0.2.0", "license": "UNLICENSED", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.758.0", - "@aws-sdk/client-elasticache": "^3.758.0", + "@aws-sdk/client-cloudwatch-logs": "^3.767.0", + "@aws-sdk/client-elasticache": "^3.761.0", "@aws-sdk/client-s3": "^3.758.0", "@aws-sdk/client-secrets-manager": "^3.758.0", "@aws-sdk/client-ses": "^3.758.0", @@ -19,36 +19,36 @@ "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", "aws4": "^1.13.2", - "axios": "^1.8.1", + "axios": "^1.8.4", "bee-queue": "^1.7.1", "better-queue": "^3.8.12", "bluebird": "^3.7.2", "body-parser": "^1.20.3", - "bullmq": "^5.41.7", + "bullmq": "^5.44.0", "chart.js": "^4.4.8", - "cloudinary": "^2.5.1", + "cloudinary": "^2.6.0", "compression": "^1.8.0", "cookie-parser": "^1.4.7", "cors": "2.8.5", "crisp-status-reporter": "^1.2.2", "csrf": "^3.1.0", - "dd-trace": "^5.40.0", + "dd-trace": "^5.43.0", "dinero.js": "^1.9.1", "dotenv": "^16.4.5", "express": "^4.21.1", - "firebase-admin": "^13.1.0", + "firebase-admin": "^13.2.0", "graphql": "^16.10.0", "graphql-request": "^6.1.0", "inline-css": "^4.0.3", "intuit-oauth": "^4.2.0", - "ioredis": "^5.5.0", - "json-2-csv": "^5.5.8", + "ioredis": "^5.6.0", + "json-2-csv": "^5.5.9", "juice": "^11.0.1", "lodash": "^4.17.21", "moment": "^2.30.1", "moment-timezone": "^0.5.47", "multer": "^1.4.5-lts.1", - "node-mailjet": "^6.0.6", + "node-mailjet": "^6.0.8", "node-persist": "^4.0.4", "nodemailer": "^6.10.0", "phone": "^3.1.58", @@ -56,7 +56,7 @@ "redis": "^4.7.0", "rimraf": "^6.0.1", "skia-canvas": "^2.0.2", - "soap": "^1.1.9", + "soap": "^1.1.10", "socket.io": "^4.8.1", "socket.io-adapter": "^2.5.5", "ssh2-sftp-client": "^11.0.0", @@ -68,10 +68,10 @@ "xmlbuilder2": "^3.1.1" }, "devDependencies": { - "@eslint/js": "^9.21.0", + "@eslint/js": "^9.22.0", "@trivago/prettier-plugin-sort-imports": "^5.2.2", "concurrently": "^8.2.2", - "eslint": "^9.21.0", + "eslint": "^9.22.0", "eslint-plugin-react": "^7.37.4", "globals": "^15.15.0", "p-limit": "^3.1.0", @@ -286,9 +286,9 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.758.0.tgz", - "integrity": "sha512-IlEIm5h4vfeoZyY8Op4W6lX1lqcEYE3DRKl+fMKRTFttvJ+AJfuZlAgFlMh9OPFQ0ZMLe8etoxHwKN50YCLivw==", + "version": "3.767.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.767.0.tgz", + "integrity": "sha512-s2XEomADqRxBuRsefNj34ce3+bmpe+80jawdOp/pXvOALKE0T9hIMy7uHlrFEQoTFskK8kx+fs7gKL3SZvuMZg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -354,9 +354,9 @@ } }, "node_modules/@aws-sdk/client-elasticache": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.758.0.tgz", - "integrity": "sha512-qmDOTHhB0hUm/Ifypi6+zjUR4dl7H576oM4/p2RUgkjyz2RgJaLJhyX32TDDzcX2maevNHJ3TijXOkGxoGDeog==", + "version": "3.761.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.761.0.tgz", + "integrity": "sha512-ZMoT1+wLT85sus8jCP4gRmsmHukmZm2GqOQOY1i+OOHTN0sXzSVneVXYmLA5c63ykoXmFyn1Oytbf/BmQUeX4w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -1327,15 +1327,15 @@ } }, "node_modules/@datadog/libdatadog": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@datadog/libdatadog/-/libdatadog-0.4.0.tgz", - "integrity": "sha512-kGZfFVmQInzt6J4FFGrqMbrDvOxqwk3WqhAreS6n9b/De+iMVy/NMu3V7uKsY5zAvz+uQw0liDJm3ZDVH/MVVw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@datadog/libdatadog/-/libdatadog-0.5.0.tgz", + "integrity": "sha512-YvLUVOhYVjJssm0f22/RnDQMc7ZZt/w1bA0nty1vvjyaDz5EWaHfWaaV4GYpCt5MRvnGjCBxIwwbRivmGseKeQ==", "license": "Apache-2.0" }, "node_modules/@datadog/native-appsec": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@datadog/native-appsec/-/native-appsec-8.4.0.tgz", - "integrity": "sha512-LC47AnpVLpQFEUOP/nIIs+i0wLb8XYO+et3ACaJlHa2YJM3asR4KZTqQjDQNy08PTAUbVvYWKwfSR1qVsU/BeA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@datadog/native-appsec/-/native-appsec-8.5.0.tgz", + "integrity": "sha512-95y+fm7jd+3iknzuu57pWEPw9fcK9uSBCPiB4kSPHszHu3bESlZM553tc4ANsz+X3gMkYGVg2pgSydG77nSDJw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1394,9 +1394,9 @@ } }, "node_modules/@datadog/pprof": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@datadog/pprof/-/pprof-5.5.1.tgz", - "integrity": "sha512-3pZVYqc5YkZJOj9Rc8kQ/wG4qlygcnnwFU/w0QKX6dEdJh+1+dWniuUu+GSEjy/H0jc14yhdT2eJJf/F2AnHNw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@datadog/pprof/-/pprof-5.6.0.tgz", + "integrity": "sha512-x7yN0s4wMnRqv3PWQ6eXKH5XE5qvCOwWbOsXqpT2Irbsc7Wcl5w5JrJUcbPCdSJGihpIh6kAeIrS6w/ZCcHy2Q==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1410,15 +1410,6 @@ "node": ">=16" } }, - "node_modules/@datadog/pprof/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, "node_modules/@datadog/sketches-js": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@datadog/sketches-js/-/sketches-js-2.1.1.tgz", @@ -1482,6 +1473,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", + "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", @@ -1533,9 +1534,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", - "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", + "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", "dev": true, "license": "MIT", "engines": { @@ -3838,9 +3839,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", - "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -3849,12 +3850,12 @@ } }, "node_modules/axios-ntlm": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/axios-ntlm/-/axios-ntlm-1.4.2.tgz", - "integrity": "sha512-8mS/uhmSWiRBiFKQvysPbX1eDBp6e+eXskmasuAXRHrn1Zjgji3O/oGXzXLw7tOhyD9nho1vGjZ2OYOD3cCvHg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/axios-ntlm/-/axios-ntlm-1.4.3.tgz", + "integrity": "sha512-CS6WE8chZpEDKxv4IFwr5zcG7InMC6Ek0aj2n2tHauBh+8KiYVC4qMn3N2arjR5tnyILQuTGlI0mc83hgWxS4Q==", "license": "MIT", "dependencies": { - "axios": "^1.6.1", + "axios": "^1.7.9", "des.js": "^1.1.0", "dev-null": "^0.1.1", "js-md4": "^0.3.2" @@ -4093,9 +4094,9 @@ } }, "node_modules/bullmq": { - "version": "5.41.7", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.41.7.tgz", - "integrity": "sha512-eZbKJSx15bflfzKRiR+dKeLTr/M/YKb4cIp73OdU79PEMHQ6aEFUtbG6R+f0KvLLznI/O01G581U2Eqli6S2ew==", + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.44.0.tgz", + "integrity": "sha512-OnEtkuXyrUx2Jm5BpH92+ttrobblBdCbkhOe3OoR0hxZuAilI3mPWlwELslhfImRpDv8rK+C/0/VK7I8f3xIig==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", @@ -4298,9 +4299,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", - "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "license": "MIT" }, "node_modules/cliui": { @@ -4337,9 +4338,9 @@ } }, "node_modules/cloudinary": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.5.1.tgz", - "integrity": "sha512-CNg6uU53Hl4FEVynkTGpt5bQEAQWDHi3H+Sm62FzKf5uQHipSN2v7qVqS8GRVqeb0T1WNV+22+75DOJeRXYeSQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.6.0.tgz", + "integrity": "sha512-FIlny9RR5LPgkMioG4V7yUpC6ASyIFQMWfx4TgOi/xBeLxJTegbyQc3itiXL0b0lDlSaL0KyT2THEw6osrKqpQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.21", @@ -4861,18 +4862,18 @@ } }, "node_modules/dd-trace": { - "version": "5.40.0", - "resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.40.0.tgz", - "integrity": "sha512-/UYVCcgpZ9LnnUvIJcNfd1Hj51i8HhqLOn9PCj5gK3wJUn6MY/ie/5da2ZaFtoK2DKQ9OZmFBITLV3+KDl4pjA==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.43.0.tgz", + "integrity": "sha512-WtPUSZfEosSHYVBFR48FqfYBFor8QchKwAKo+LYtbgTPtFzYKyBV/FJUqYE6sDF15Raf4sJVt/LOscywgj2zEw==", "hasInstallScript": true, "license": "(Apache-2.0 OR BSD-3-Clause)", "dependencies": { - "@datadog/libdatadog": "^0.4.0", - "@datadog/native-appsec": "8.4.0", + "@datadog/libdatadog": "^0.5.0", + "@datadog/native-appsec": "8.5.0", "@datadog/native-iast-rewriter": "2.8.0", "@datadog/native-iast-taint-tracking": "3.3.0", "@datadog/native-metrics": "^3.1.0", - "@datadog/pprof": "5.5.1", + "@datadog/pprof": "5.6.0", "@datadog/sketches-js": "^2.1.0", "@isaacs/ttlcache": "^1.4.1", "@opentelemetry/api": ">=1.0.0 <1.9.0", @@ -4880,7 +4881,7 @@ "crypto-randomuuid": "^1.0.0", "dc-polyfill": "^0.1.4", "ignore": "^5.2.4", - "import-in-the-middle": "1.11.2", + "import-in-the-middle": "1.13.1", "istanbul-lib-coverage": "3.2.0", "jest-docblock": "^29.7.0", "koalas": "^1.0.2", @@ -4904,15 +4905,6 @@ "node": ">=18" } }, - "node_modules/dd-trace/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -5585,18 +5577,19 @@ } }, "node_modules/eslint": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", - "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", + "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", + "@eslint/config-helpers": "^0.1.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.21.0", + "@eslint/js": "9.22.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5608,7 +5601,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", + "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -5688,9 +5681,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -6089,9 +6082,9 @@ } }, "node_modules/firebase-admin": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.1.0.tgz", - "integrity": "sha512-XPKiTyPyvUMZ22EPk4M1oSiZ8/4qFeYwjK88o/DYpGtNbOLKrM6Oc9jTaK+P6Vwn3Vr1+OCyLLJ93Bci382UqA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.2.0.tgz", + "integrity": "sha512-qQBTKo0QWCDaWwISry989pr8YfZSSk00rNCKaucjOgltEm3cCYzEe4rODqBd1uUwma+Iu5jtAzg89Nfsjr3fGg==", "license": "Apache-2.0", "dependencies": { "@fastify/busboy": "^3.0.0", @@ -6959,12 +6952,12 @@ } }, "node_modules/import-in-the-middle": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.11.2.tgz", - "integrity": "sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz", + "integrity": "sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA==", "license": "Apache-2.0", "dependencies": { - "acorn": "^8.8.2", + "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" @@ -7051,9 +7044,9 @@ } }, "node_modules/ioredis": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.5.0.tgz", - "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz", + "integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==", "license": "MIT", "dependencies": { "@ioredis/commands": "^1.1.1", @@ -7627,9 +7620,9 @@ } }, "node_modules/json-2-csv": { - "version": "5.5.8", - "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.8.tgz", - "integrity": "sha512-eMQHOwV+av8Sgo+fkbEbQWOw/kwh89AZ5fNA8TYfcooG6TG1ZOL2WcPUrngIMIK8dBJitQ8QEU0zbncQ0CX4CQ==", + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/json-2-csv/-/json-2-csv-5.5.9.tgz", + "integrity": "sha512-l4g6GZVHrsN+5SKkpOmGNSvho+saDZwXzj/xmcO0lJAgklzwsiqy70HS5tA9djcRvBEybZ9IF6R1MDFTEsaOGQ==", "license": "MIT", "dependencies": { "deeks": "3.1.0", @@ -8421,12 +8414,12 @@ } }, "node_modules/node-mailjet": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/node-mailjet/-/node-mailjet-6.0.6.tgz", - "integrity": "sha512-cr8ciqtHuxyFd3+3bpDy+oKuNzctZfRQZtwRjurVAzE+DZLTfyxjgD+GTqQ1kr0ClAjDjSh3ERlZvd5MV0fKHg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/node-mailjet/-/node-mailjet-6.0.8.tgz", + "integrity": "sha512-VyB2+SeD1zuxpuJLePC4bk10UN0G294CsVlF8YBxb+1tlH0Tw4wECGlTYlQOaRMtPgckdObpcfEbhUWu3UuFIQ==", "license": "MIT", "dependencies": { - "axios": "1.7.4", + "axios": "^1.8.1", "json-bigint": "^1.0.0", "url-join": "^4.0.0" }, @@ -8435,17 +8428,6 @@ "npm": ">= 6.9.0" } }, - "node_modules/node-mailjet/node_modules/axios": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/node-persist": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-4.0.4.tgz", @@ -9920,13 +9902,13 @@ } }, "node_modules/soap": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/soap/-/soap-1.1.9.tgz", - "integrity": "sha512-x6wMhwIwGFnMQiV0tLIygERELwpV/EkidUvzjcCPRx0D16YngNL8z7j5+nFad0Fl5irisXbfY2FKzvF9SEjMog==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/soap/-/soap-1.1.10.tgz", + "integrity": "sha512-dqfX9qHhXup3ZLWsI5of6xJIJKeBCPnn3tTu9sKtASm2A53Zk6/u3drygLiUy+H1mmjRBptXfVkjY6pt8nhOjA==", "license": "MIT", "dependencies": { - "axios": "^1.7.9", - "axios-ntlm": "^1.4.2", + "axios": "^1.8.3", + "axios-ntlm": "^1.4.3", "debug": "^4.4.0", "formidable": "^3.5.2", "get-stream": "^6.0.1", @@ -9934,7 +9916,7 @@ "sax": "^1.4.1", "strip-bom": "^3.0.0", "whatwg-mimetype": "4.0.0", - "xml-crypto": "^6.0.0" + "xml-crypto": "^6.0.1" }, "engines": { "node": ">=14.17.0" @@ -10032,6 +10014,15 @@ } } }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/source-map-explorer": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.3.tgz", @@ -10094,16 +10085,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/source-map-explorer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, "node_modules/source-map-explorer/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -11487,9 +11468,9 @@ } }, "node_modules/xml-crypto": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.0.tgz", - "integrity": "sha512-L3RgnkaDrHaYcCnoENv4Idzt1ZRj5U1z1BDH98QdDTQfssScx8adgxhd9qwyYo+E3fXbQZjEQH7aiXHLVgxGvw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.1.tgz", + "integrity": "sha512-v05aU7NS03z4jlZ0iZGRFeZsuKO1UfEbbYiaeRMiATBFs6Jq9+wqKquEMTn4UTrYZ9iGD8yz3KT4L9o2iF682w==", "license": "MIT", "dependencies": { "@xmldom/is-dom-node": "^1.0.1", diff --git a/package.json b/package.json index 6f0426a26..ad845ca08 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.758.0", - "@aws-sdk/client-elasticache": "^3.758.0", + "@aws-sdk/client-cloudwatch-logs": "^3.767.0", + "@aws-sdk/client-elasticache": "^3.761.0", "@aws-sdk/client-s3": "^3.758.0", "@aws-sdk/client-secrets-manager": "^3.758.0", "@aws-sdk/client-ses": "^3.758.0", @@ -29,36 +29,36 @@ "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", "aws4": "^1.13.2", - "axios": "^1.8.1", + "axios": "^1.8.4", "bee-queue": "^1.7.1", "better-queue": "^3.8.12", "bluebird": "^3.7.2", "body-parser": "^1.20.3", - "bullmq": "^5.41.7", + "bullmq": "^5.44.0", "chart.js": "^4.4.8", - "cloudinary": "^2.5.1", + "cloudinary": "^2.6.0", "compression": "^1.8.0", "cookie-parser": "^1.4.7", "cors": "2.8.5", "crisp-status-reporter": "^1.2.2", "csrf": "^3.1.0", - "dd-trace": "^5.40.0", + "dd-trace": "^5.43.0", "dinero.js": "^1.9.1", "dotenv": "^16.4.5", "express": "^4.21.1", - "firebase-admin": "^13.1.0", + "firebase-admin": "^13.2.0", "graphql": "^16.10.0", "graphql-request": "^6.1.0", "inline-css": "^4.0.3", "intuit-oauth": "^4.2.0", - "ioredis": "^5.5.0", - "json-2-csv": "^5.5.8", + "ioredis": "^5.6.0", + "json-2-csv": "^5.5.9", "juice": "^11.0.1", "lodash": "^4.17.21", "moment": "^2.30.1", "moment-timezone": "^0.5.47", "multer": "^1.4.5-lts.1", - "node-mailjet": "^6.0.6", + "node-mailjet": "^6.0.8", "node-persist": "^4.0.4", "nodemailer": "^6.10.0", "phone": "^3.1.58", @@ -66,7 +66,7 @@ "redis": "^4.7.0", "rimraf": "^6.0.1", "skia-canvas": "^2.0.2", - "soap": "^1.1.9", + "soap": "^1.1.10", "socket.io": "^4.8.1", "socket.io-adapter": "^2.5.5", "ssh2-sftp-client": "^11.0.0", @@ -78,10 +78,10 @@ "xmlbuilder2": "^3.1.1" }, "devDependencies": { - "@eslint/js": "^9.21.0", + "@eslint/js": "^9.22.0", "@trivago/prettier-plugin-sort-imports": "^5.2.2", "concurrently": "^8.2.2", - "eslint": "^9.21.0", + "eslint": "^9.22.0", "eslint-plugin-react": "^7.37.4", "globals": "^15.15.0", "p-limit": "^3.1.0", From 85f1d5cae217b7228d1a4af64bb2f27e3782b379 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 11:04:07 -0400 Subject: [PATCH 14/33] IO-3181-Testing-Framework-Selection: Remove Cypress, upgrade Split --- client/cypress.config.js | 17 -- .../e2e/01-General Render/01-home.cy.js | 19 -- .../cypress/e2e/1-getting-started/todo.cy.js | 124 -------- .../e2e/2-advanced-examples/actions.cy.js | 284 ------------------ .../e2e/2-advanced-examples/aliasing.cy.js | 35 --- .../e2e/2-advanced-examples/assertions.cy.js | 173 ----------- .../e2e/2-advanced-examples/connectors.cy.js | 96 ------ .../e2e/2-advanced-examples/cookies.cy.js | 79 ----- .../e2e/2-advanced-examples/cypress_api.cy.js | 208 ------------- .../e2e/2-advanced-examples/files.cy.js | 86 ------ .../2-advanced-examples/local_storage.cy.js | 58 ---- .../e2e/2-advanced-examples/location.cy.js | 32 -- .../e2e/2-advanced-examples/misc.cy.js | 98 ------ .../e2e/2-advanced-examples/navigation.cy.js | 56 ---- .../network_requests.cy.js | 165 ---------- .../e2e/2-advanced-examples/querying.cy.js | 100 ------ .../spies_stubs_clocks.cy.js | 203 ------------- .../e2e/2-advanced-examples/traversal.cy.js | 97 ------ .../e2e/2-advanced-examples/utilities.cy.js | 108 ------- .../e2e/2-advanced-examples/viewport.cy.js | 59 ---- .../e2e/2-advanced-examples/waiting.cy.js | 31 -- .../e2e/2-advanced-examples/window.cy.js | 22 -- client/cypress/fixtures/example.json | 5 - client/cypress/fixtures/profile.json | 5 - client/cypress/fixtures/users.json | 1 - client/cypress/plugins/index.js | 22 -- client/cypress/support/commands.js | 27 -- client/cypress/support/e2e.js | 20 -- client/cypress/tsconfig.json | 8 - client/package-lock.json | 169 +++++------ client/package.json | 6 +- client/src/App/App.container.jsx | 32 +- client/src/redux/user/user.actions.js | 5 + client/src/redux/user/user.reducer.js | 5 + client/src/redux/user/user.sagas.js | 5 +- client/src/redux/user/user.types.js | 3 +- 36 files changed, 127 insertions(+), 2336 deletions(-) delete mode 100644 client/cypress.config.js delete mode 100644 client/cypress/e2e/01-General Render/01-home.cy.js delete mode 100644 client/cypress/e2e/1-getting-started/todo.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/actions.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/aliasing.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/assertions.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/connectors.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/cookies.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/cypress_api.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/files.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/local_storage.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/location.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/misc.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/navigation.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/network_requests.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/querying.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/spies_stubs_clocks.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/traversal.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/utilities.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/viewport.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/waiting.cy.js delete mode 100644 client/cypress/e2e/2-advanced-examples/window.cy.js delete mode 100644 client/cypress/fixtures/example.json delete mode 100644 client/cypress/fixtures/profile.json delete mode 100644 client/cypress/fixtures/users.json delete mode 100644 client/cypress/plugins/index.js delete mode 100644 client/cypress/support/commands.js delete mode 100644 client/cypress/support/e2e.js delete mode 100644 client/cypress/tsconfig.json diff --git a/client/cypress.config.js b/client/cypress.config.js deleted file mode 100644 index b000b5aac..000000000 --- a/client/cypress.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const { defineConfig } = require("cypress"); - -module.exports = defineConfig({ - experimentalStudio: true, - env: { - FIREBASE_USERNAME: "cypress@imex.test", - FIREBASE_PASSWORD: "cypress" - }, - e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. - setupNodeEvents(on, config) { - return require("./cypress/plugins/index.js")(on, config); - }, - baseUrl: "https://localhost:3000" - } -}); diff --git a/client/cypress/e2e/01-General Render/01-home.cy.js b/client/cypress/e2e/01-General Render/01-home.cy.js deleted file mode 100644 index 44202154f..000000000 --- a/client/cypress/e2e/01-General Render/01-home.cy.js +++ /dev/null @@ -1,19 +0,0 @@ -/// -const { FIREBASE_USERNAME, FIREBASE_PASSWORcD } = Cypress.env(); -describe("Renders the General Page", () => { - beforeEach(() => { - cy.visit("/"); - }); - it("Renders Correctly", () => {}); - it("Has the Slogan", () => { - cy.findByText("A whole x22new kind of shop management system.").should("exist"); - /* ==== Generated with Cypress Studio ==== */ - cy.get(".ant-menu-item-active > .ant-menu-title-content > .header0-item-block").click(); - cy.get("#email").clear(); - cy.get("#email").type("patrick@imex.dev"); - cy.get("#password").clear(); - cy.get("#password").type("patrick123{enter}"); - cy.get(".ant-form > .ant-btn").click(); - /* ==== End Cypress Studio ==== */ - }); -}); diff --git a/client/cypress/e2e/1-getting-started/todo.cy.js b/client/cypress/e2e/1-getting-started/todo.cy.js deleted file mode 100644 index 87e609ced..000000000 --- a/client/cypress/e2e/1-getting-started/todo.cy.js +++ /dev/null @@ -1,124 +0,0 @@ -/// - -// Welcome to Cypress! -// -// This spec file contains a variety of sample tests -// for a todo list app that are designed to demonstrate -// the power of writing tests in Cypress. -// -// To learn more about how Cypress works and -// what makes it such an awesome testing tool, -// please read our getting started guide: -// https://on.cypress.io/introduction-to-cypress - -describe("example to-do app", () => { - beforeEach(() => { - // Cypress starts out with a blank slate for each test - // so we must tell it to visit our website with the `cy.visit()` command. - // Since we want to visit the same URL at the start of all our tests, - // we include it in our beforeEach function so that it runs before each test - cy.visit("https://example.cypress.io/todo"); - }); - - it("displays two todo items by default", () => { - // We use the `cy.get()` command to get all elements that match the selector. - // Then, we use `should` to assert that there are two matched items, - // which are the two default items. - cy.get(".todo-list li").should("have.length", 2); - - // We can go even further and check that the default todos each contain - // the correct text. We use the `first` and `last` functions - // to get just the first and last matched elements individually, - // and then perform an assertion with `should`. - cy.get(".todo-list li").first().should("have.text", "Pay electric bill"); - cy.get(".todo-list li").last().should("have.text", "Walk the dog"); - }); - - it("can add new todo items", () => { - // We'll store our item text in a variable so we can reuse it - const newItem = "Feed the cat"; - - // Let's get the input element and use the `type` command to - // input our new list item. After typing the content of our item, - // we need to type the enter key as well in order to submit the input. - // This input has a data-test attribute so we'll use that to select the - // element in accordance with best practices: - // https://on.cypress.io/selecting-elements - cy.get("[data-test=new-todo]").type(`${newItem}{enter}`); - - // Now that we've typed our new item, let's check that it actually was added to the list. - // Since it's the newest item, it should exist as the last element in the list. - // In addition, with the two default items, we should have a total of 3 elements in the list. - // Since assertions yield the element that was asserted on, - // we can chain both of these assertions together into a single statement. - cy.get(".todo-list li").should("have.length", 3).last().should("have.text", newItem); - }); - - it("can check off an item as completed", () => { - // In addition to using the `get` command to get an element by selector, - // we can also use the `contains` command to get an element by its contents. - // However, this will yield the
+
+ ); + }} + + + ); } diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index ddfd26a2f..a9f16c111 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -509,6 +509,7 @@ export const GET_JOB_BY_PK = gql` est_ct_ln est_ea est_ph1 + flat_rate_ats federal_tax_rate id inproduction @@ -649,6 +650,7 @@ export const GET_JOB_BY_PK = gql` policy_no production_vars rate_ats + rate_ats_flat rate_la1 rate_la2 rate_la3 diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 5e062169a..ccf90fc10 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -722,6 +722,7 @@ "scoreboardsetup": "Scoreboard Setup", "shop_enabled_features": "Shop Enabled Features", "shopinfo": "Shop Information", + "shoprates": "Shop Rates", "speedprint": "Speed Print Configuration", "ssbuckets": "Job Size Definitions", "systemsettings": "System Settings", @@ -1658,7 +1659,7 @@ "08": "Left Rear Side", "09": "Left Side" }, - "auto_add_ats": "Automatically Add/Update ATS", + "auto_add_ats": "Automatically Add/Update ATS?", "ca_bc_pvrt": "PVRT", "ca_customer_gst": "Customer Portion of GST", "ca_gst_registrant": "GST Registrant", @@ -1757,6 +1758,7 @@ "est_ct_ln": "Estimator Last Name", "est_ea": "Estimator Email", "est_ph1": "Estimator Phone #", + "flat_rate_ats": "Flat Rate ATS?", "federal_tax_payable": "Federal Tax Payable", "federal_tax_rate": "Federal Tax Rate", "ins_addr1": "Insurance Co. Address", @@ -1868,6 +1870,7 @@ }, "queued_for_parts": "Queued for Parts", "rate_ats": "ATS Rate", + "rate_ats_flat": "ATS Flat Rate", "rate_la1": "LA1", "rate_la2": "LA2", "rate_la3": "LA3", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 6aa64961a..2f9fa9a2b 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -722,6 +722,7 @@ "scoreboardsetup": "", "shop_enabled_features": "", "shopinfo": "", + "shoprates": "", "speedprint": "", "ssbuckets": "", "systemsettings": "", @@ -1757,6 +1758,7 @@ "est_ct_ln": "Apellido del tasador", "est_ea": "Correo electrónico del tasador", "est_ph1": "Número de teléfono del tasador", + "flat_rate_ats": "", "federal_tax_payable": "Impuesto federal por pagar", "federal_tax_rate": "", "ins_addr1": "Dirección de Insurance Co.", @@ -1868,6 +1870,7 @@ }, "queued_for_parts": "", "rate_ats": "", + "rate_ats_flat": "", "rate_la1": "Tarifa LA1", "rate_la2": "Tarifa LA2", "rate_la3": "Tarifa LA3", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 6e9713285..888162de9 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -722,6 +722,7 @@ "scoreboardsetup": "", "shop_enabled_features": "", "shopinfo": "", + "shoprates": "", "speedprint": "", "ssbuckets": "", "systemsettings": "", @@ -1757,6 +1758,7 @@ "est_ct_ln": "Nom de l'évaluateur", "est_ea": "Courriel de l'évaluateur", "est_ph1": "Numéro de téléphone de l'évaluateur", + "flat_rate_ats": "", "federal_tax_payable": "Impôt fédéral à payer", "federal_tax_rate": "", "ins_addr1": "Adresse Insurance Co.", @@ -1868,6 +1870,7 @@ }, "queued_for_parts": "", "rate_ats": "", + "rate_ats_flat": "", "rate_la1": "Taux LA1", "rate_la2": "Taux LA2", "rate_la3": "Taux LA3", diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 6cdb4c008..4d1f5192e 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -3695,6 +3695,7 @@ - est_st - est_zip - federal_tax_rate + - flat_rate_ats - g_bett_amt - id - inproduction @@ -3789,6 +3790,7 @@ - qb_multiple_payers - queued_for_parts - rate_ats + - rate_ats_flat - rate_la1 - rate_la2 - rate_la3 @@ -3965,6 +3967,7 @@ - est_st - est_zip - federal_tax_rate + - flat_rate_ats - g_bett_amt - id - inproduction @@ -4060,6 +4063,7 @@ - qb_multiple_payers - queued_for_parts - rate_ats + - rate_ats_flat - rate_la1 - rate_la2 - rate_la3 @@ -4247,6 +4251,7 @@ - est_st - est_zip - federal_tax_rate + - flat_rate_ats - g_bett_amt - id - inproduction @@ -4342,6 +4347,7 @@ - qb_multiple_payers - queued_for_parts - rate_ats + - rate_ats_flat - rate_la1 - rate_la2 - rate_la3 diff --git a/hasura/migrations/1742583400542_alter_table_public_jobs_add_column_flat_rate_ats/down.sql b/hasura/migrations/1742583400542_alter_table_public_jobs_add_column_flat_rate_ats/down.sql new file mode 100644 index 000000000..5478f998b --- /dev/null +++ b/hasura/migrations/1742583400542_alter_table_public_jobs_add_column_flat_rate_ats/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."jobs" add column "flat_rate_ats" boolean +-- null default 'false'; diff --git a/hasura/migrations/1742583400542_alter_table_public_jobs_add_column_flat_rate_ats/up.sql b/hasura/migrations/1742583400542_alter_table_public_jobs_add_column_flat_rate_ats/up.sql new file mode 100644 index 000000000..73fa874ae --- /dev/null +++ b/hasura/migrations/1742583400542_alter_table_public_jobs_add_column_flat_rate_ats/up.sql @@ -0,0 +1,2 @@ +alter table "public"."jobs" add column "flat_rate_ats" boolean + null default 'false'; diff --git a/hasura/migrations/1742583433746_alter_table_public_jobs_add_column_rate_ats_flat/down.sql b/hasura/migrations/1742583433746_alter_table_public_jobs_add_column_rate_ats_flat/down.sql new file mode 100644 index 000000000..3f82e9770 --- /dev/null +++ b/hasura/migrations/1742583433746_alter_table_public_jobs_add_column_rate_ats_flat/down.sql @@ -0,0 +1,4 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- alter table "public"."jobs" add column "rate_ats_flat" numeric +-- null; diff --git a/hasura/migrations/1742583433746_alter_table_public_jobs_add_column_rate_ats_flat/up.sql b/hasura/migrations/1742583433746_alter_table_public_jobs_add_column_rate_ats_flat/up.sql new file mode 100644 index 000000000..e0157edd2 --- /dev/null +++ b/hasura/migrations/1742583433746_alter_table_public_jobs_add_column_rate_ats_flat/up.sql @@ -0,0 +1,2 @@ +alter table "public"."jobs" add column "rate_ats_flat" numeric + null; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index cc50b3115..3df3e810c 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1485,6 +1485,8 @@ exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) { materials auto_add_ats rate_ats + flat_rate_ats + rate_ats_flat joblines(where: { removed: { _eq: false } }){ id line_no diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 4e889a658..6835dc7cb 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -1,7 +1,7 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); -const adminClient = require("../graphql-client/graphql-client").client; -const _ = require("lodash"); +// const adminClient = require("../graphql-client/graphql-client").client; +// const _ = require("lodash"); const logger = require("../utils/logger"); const InstanceMgr = require("../utils/instanceMgr").default; @@ -45,7 +45,9 @@ exports.totalsSsu = async function (req, res) { } }); - res.status(200).send(); + if (result) { + res.status(200).send(); + } } catch (error) { logger.log("job-totals-ssu-USA-error", "ERROR", req?.user?.email, id, { jobid: id, @@ -59,7 +61,7 @@ exports.totalsSsu = async function (req, res) { //IMPORTANT*** These two functions MUST be mirrrored. async function TotalsServerSide(req, res) { const { job, client } = req.body; - await AutoAddAtsIfRequired({ job: job, client: client }); + await AtsAdjustmentsIfRequired({ job: job, client: client, user: req?.user }); try { let ret = { @@ -137,11 +139,12 @@ async function Totals(req, res) { const logger = req.logger; const client = req.userGraphQLClient; - logger.log("job-totals-ssu-USA", "DEBUG", req.user.email, job.id, { - jobid: job.id + logger.log("job-totals-USA", "DEBUG", req.user.email, job.id, { + jobid: job.id, + id: id }); - await AutoAddAtsIfRequired({ job, client }); + await AtsAdjustmentsIfRequired({ job, client, user: req.user }); try { let ret = { @@ -162,40 +165,45 @@ async function Totals(req, res) { } } -async function AutoAddAtsIfRequired({ job, client }) { - //Check if ATS should be automatically added. - if (job.auto_add_ats) { - //Get the total sum of hours that should be the ATS amount. - //Check to see if an ATS line exists. +async function AtsAdjustmentsIfRequired({ job, client, user }) { + if (job.auto_add_ats || job.flat_rate_ats) { + let atsAmount = 0; let atsLineIndex = null; - const atsHours = job.joblines.reduce((acc, val, index) => { - if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") { - atsLineIndex = index; - } - if ( - val.mod_lbr_ty !== "LA1" && - val.mod_lbr_ty !== "LA2" && - val.mod_lbr_ty !== "LA3" && - val.mod_lbr_ty !== "LA4" && - val.mod_lbr_ty !== "LAU" && - val.mod_lbr_ty !== "LAG" && - val.mod_lbr_ty !== "LAS" && - val.mod_lbr_ty !== "LAA" - ) { - acc = acc + val.mod_lb_hrs; - } + //Check if ATS should be automatically added. + if (job.auto_add_ats) { + const excludedLaborTypes = new Set(["LAA", "LAG", "LAS", "LAU", "LA1", "LA2", "LA3", "LA4"]); - return acc; - }, 0); + //Get the total sum of hours that should be the ATS amount. + //Check to see if an ATS line exists. + const atsHours = job.joblines.reduce((acc, val, index) => { + if (val.line_desc?.toLowerCase() === "ats amount") { + atsLineIndex = index; + } - const atsAmount = atsHours * (job.rate_ats || 0); - //If it does, update it in place, and make sure it is updated for local calculations. + if (!excludedLaborTypes.has(val.mod_lbr_ty)) { + acc = acc + val.mod_lb_hrs; + } + + return acc; + }, 0); + + atsAmount = atsHours * (job.rate_ats || 0); + } + + //Check if a Flat Rate ATS should be added. + if (job.flat_rate_ats) { + atsLineIndex = ((i) => (i === -1 ? null : i))( + job.joblines.findIndex((line) => line.line_desc?.toLowerCase() === "ats amount") + ); + atsAmount = job.rate_ats_flat || 0; + } + + //If it does not, create one for local calculations and insert it. if (atsLineIndex === null) { const newAtsLine = { jobid: job.id, alt_partm: null, - line_no: 35, unq_seq: 0, line_ind: "E", line_desc: "ATS Amount", @@ -220,19 +228,40 @@ async function AutoAddAtsIfRequired({ job, client }) { prt_dsmk_m: 0.0 }; - const result = await client.request(queries.INSERT_NEW_JOB_LINE, { - lineInput: [newAtsLine] - }); + try { + const result = await client.request(queries.INSERT_NEW_JOB_LINE, { + lineInput: [newAtsLine] + }); - job.joblines.push(newAtsLine); + if (result) { + job.joblines.push(newAtsLine); + } + } catch (error) { + logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, { + jobid: job.id, + error: error.message, + stack: error.stack + }); + } } - //If it does not, create one for local calculations and insert it. + //If it does, update it in place, and make sure it is updated for local calculations. else { - const result = await client.request(queries.UPDATE_JOB_LINE, { - line: { act_price: atsAmount }, - lineId: job.joblines[atsLineIndex].id - }); - job.joblines[atsLineIndex].act_price = atsAmount; + try { + const result = await client.request(queries.UPDATE_JOB_LINE, { + line: { act_price: atsAmount }, + lineId: job.joblines[atsLineIndex].id + }); + if (result) { + job.joblines[atsLineIndex].act_price = atsAmount; + } + } catch (error) { + logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, { + jobid: job.id, + atsLineIndex: atsLineIndex, + error: error.message, + stack: error.stack + }); + } } } } @@ -314,7 +343,7 @@ async function CalculateRatesTotals({ job, client }) { let hasMashLine = false; let hasMahwLine = false; let hasCustomMahwLine; - let mapaOpCodes = ParseCalopCode(job.materials["MAPA"]?.cal_opcode); + // let mapaOpCodes = ParseCalopCode(job.materials["MAPA"]?.cal_opcode); let mashOpCodes = ParseCalopCode(job.materials["MASH"]?.cal_opcode); jobLines.forEach((item) => { @@ -564,7 +593,7 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) { } }; - default: + default: { if (!value.part_type && value.db_ref !== "900510" && value.db_ref !== "900511") return acc; const discountAmount = @@ -631,6 +660,7 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) { ) } }; + } } }, { @@ -652,7 +682,7 @@ function CalculatePartsTotals(jobLines, parts_tax_rates, job) { let adjustments = {}; //Track all adjustments that need to be made. - const linesToAdjustForDiscount = []; + //const linesToAdjustForDiscount = []; Object.keys(parts_tax_rates).forEach((key) => { //Check if there's a discount or a mark up. let disc = Dinero(), @@ -1019,7 +1049,9 @@ function CalculateTaxesTotals(job, otherTotals) { } } catch (error) { logger.log("job-totals-USA Key with issue", "error", null, job.id, { - key + key: key, + error: error.message, + stack: error.stack }); } }); @@ -1158,6 +1190,8 @@ function CalculateTaxesTotals(job, otherTotals) { exports.default = Totals; function DiscountNotAlreadyCounted(jobline, joblines) { + void jobline; + void joblines; return false; } @@ -1172,27 +1206,35 @@ function IsTrueOrYes(value) { return value === true || value === "Y" || value === "y"; } -async function UpdateJobLines(joblinesToUpdate) { - if (joblinesToUpdate.length === 0) return; - const updateQueries = joblinesToUpdate.map((line, index) => - generateUpdateQuery(_.pick(line, ["id", "prt_dsmk_m", "prt_dsmk_p"]), index) - ); - const query = ` - mutation UPDATE_EST_LINES{ - ${updateQueries} - } - `; +// Function not in use from RO to IO Merger 02/05/2024 +// async function UpdateJobLines(joblinesToUpdate) { +// if (joblinesToUpdate.length === 0) return; +// const updateQueries = joblinesToUpdate.map((line, index) => +// generateUpdateQuery(_.pick(line, ["id", "prt_dsmk_m", "prt_dsmk_p"]), index) +// ); +// const query = ` +// mutation UPDATE_EST_LINES{ +// ${updateQueries} +// } +// `; +// try { +// const result = await adminClient.request(query); +// void result; +// } catch (error) { +// logger.log("update-job-lines", "error", null, null, { +// error: error.message, +// stack: error.stack +// }); +// } +// } - const result = await adminClient.request(query); -} - -const generateUpdateQuery = (lineToUpdate, index) => { - return ` - update_joblines${index}: update_joblines(where: { id: { _eq: "${ - lineToUpdate.id - }" } }, _set: ${JSON.stringify(lineToUpdate).replace(/"(\w+)"\s*:/g, "$1:")}) { - returning { - id - } - }`; -}; +// const generateUpdateQuery = (lineToUpdate, index) => { +// return ` +// update_joblines${index}: update_joblines(where: { id: { _eq: "${ +// lineToUpdate.id +// }" } }, _set: ${JSON.stringify(lineToUpdate).replace(/"(\w+)"\s*:/g, "$1:")}) { +// returning { +// id +// } +// }`; +// }; diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 4cd45e13c..0a160426d 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -1,7 +1,5 @@ const Dinero = require("dinero.js"); const queries = require("../graphql-client/queries"); -const adminClient = require("../graphql-client/graphql-client").client; -const _ = require("lodash"); const logger = require("../utils/logger"); //****************************************************** */ @@ -44,11 +42,16 @@ exports.totalsSsu = async function (req, res) { } }); + if (!result) { + throw new Error("Failed to update job totals"); + } + res.status(200).send(); } catch (error) { logger.log("job-totals-ssu-error", "ERROR", req.user.email, id, { jobid: id, - error + error: error.message, + stack: error.stack }); res.status(503).send(); } @@ -57,7 +60,7 @@ exports.totalsSsu = async function (req, res) { //IMPORTANT*** These two functions MUST be mirrrored. async function TotalsServerSide(req, res) { const { job, client } = req.body; - await AutoAddAtsIfRequired({ job: job, client: client }); + await AtsAdjustmentsIfRequired({ job: job, client: client, user: req?.user }); try { let ret = { @@ -71,7 +74,8 @@ async function TotalsServerSide(req, res) { } catch (error) { logger.log("job-totals-ssu-error", "ERROR", req?.user?.email, job.id, { jobid: job.id, - error + error: error.message, + stack: error.stack }); res.status(400).send(JSON.stringify(error)); } @@ -83,13 +87,12 @@ async function Totals(req, res) { const logger = req.logger; const client = req.userGraphQLClient; - logger.log("job-totals", "DEBUG", req.user.email, job.id, { - jobid: job.id + logger.log("job-totals-", "DEBUG", req.user.email, job.id, { + jobid: job.id, + id: id }); - logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null); - - await AutoAddAtsIfRequired({ job, client }); + await AtsAdjustmentsIfRequired({ job, client, user: req.user }); try { let ret = { @@ -103,46 +106,52 @@ async function Totals(req, res) { } catch (error) { logger.log("job-totals-error", "ERROR", req.user.email, job.id, { jobid: job.id, - error + error: error.message, + stack: error.stack }); res.status(400).send(JSON.stringify(error)); } } -async function AutoAddAtsIfRequired({ job, client }) { - //Check if ATS should be automatically added. - if (job.auto_add_ats) { - //Get the total sum of hours that should be the ATS amount. - //Check to see if an ATS line exists. +async function AtsAdjustmentsIfRequired({ job, client, user }) { + if (job.auto_add_ats || job.flat_rate_ats) { + let atsAmount = 0; let atsLineIndex = null; - const atsHours = job.joblines.reduce((acc, val, index) => { - if (val.line_desc && val.line_desc.toLowerCase() === "ats amount") { - atsLineIndex = index; - } - if ( - val.mod_lbr_ty !== "LA1" && - val.mod_lbr_ty !== "LA2" && - val.mod_lbr_ty !== "LA3" && - val.mod_lbr_ty !== "LA4" && - val.mod_lbr_ty !== "LAU" && - val.mod_lbr_ty !== "LAG" && - val.mod_lbr_ty !== "LAS" && - val.mod_lbr_ty !== "LAA" - ) { - acc = acc + val.mod_lb_hrs; - } + //Check if ATS should be automatically added. + if (job.auto_add_ats) { + const excludedLaborTypes = new Set(["LAA", "LAG", "LAS", "LAU", "LA1", "LA2", "LA3", "LA4"]); - return acc; - }, 0); + //Get the total sum of hours that should be the ATS amount. + //Check to see if an ATS line exists. + const atsHours = job.joblines.reduce((acc, val, index) => { + if (val.line_desc?.toLowerCase() === "ats amount") { + atsLineIndex = index; + } - const atsAmount = atsHours * (job.rate_ats || 0); - //If it does, update it in place, and make sure it is updated for local calculations. + if (!excludedLaborTypes.has(val.mod_lbr_ty)) { + acc = acc + val.mod_lb_hrs; + } + + return acc; + }, 0); + + atsAmount = atsHours * (job.rate_ats || 0); + } + + //Check if a Flat Rate ATS should be added. + if (job.flat_rate_ats) { + atsLineIndex = ((i) => (i === -1 ? null : i))( + job.joblines.findIndex((line) => line.line_desc?.toLowerCase() === "ats amount") + ); + atsAmount = job.rate_ats_flat || 0; + } + + //If it does not, create one for local calculations and insert it. if (atsLineIndex === null) { const newAtsLine = { jobid: job.id, alt_partm: null, - line_no: 35, unq_seq: 0, line_ind: "E", line_desc: "ATS Amount", @@ -167,22 +176,41 @@ async function AutoAddAtsIfRequired({ job, client }) { prt_dsmk_m: 0.0 }; - const result = await client.request(queries.INSERT_NEW_JOB_LINE, { - lineInput: [newAtsLine] - }); + try { + const result = await client.request(queries.INSERT_NEW_JOB_LINE, { + lineInput: [newAtsLine] + }); - job.joblines.push(newAtsLine); + if (result) { + job.joblines.push(newAtsLine); + } + } catch (error) { + logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, { + jobid: job.id, + error: error.message, + stack: error.stack + }); + } } - //If it does not, create one for local calculations and insert it. + //If it does, update it in place, and make sure it is updated for local calculations. else { - const result = await client.request(queries.UPDATE_JOB_LINE, { - line: { act_price: atsAmount }, - lineId: job.joblines[atsLineIndex].id - }); - job.joblines[atsLineIndex].act_price = atsAmount; + try { + const result = await client.request(queries.UPDATE_JOB_LINE, { + line: { act_price: atsAmount }, + lineId: job.joblines[atsLineIndex].id + }); + if (result) { + job.joblines[atsLineIndex].act_price = atsAmount; + } + } catch (error) { + logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, { + jobid: job.id, + atsLineIndex: atsLineIndex, + error: error.message, + stack: error.stack + }); + } } - - //console.log(job.jobLines); } } From f3b9c6399f0f66142ef6e6ed3a6efc686866d00b Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 13:10:00 -0400 Subject: [PATCH 20/33] IO-3183-Dependency-Updates-and-maintenance - UUID, Concurrently and Twilio Updates --- package-lock.json | 93 ++++----------------- package.json | 6 +- server/accounting/pbs/pbs-ap-allocations.js | 3 +- server/data/arms.js | 4 +- server/routes/miscellaneousRoutes.js | 4 +- 5 files changed, 25 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d0fc24da..0db4ef983 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,8 +60,8 @@ "socket.io": "^4.8.1", "socket.io-adapter": "^2.5.5", "ssh2-sftp-client": "^11.0.0", - "twilio": "^4.23.0", - "uuid": "^10.0.0", + "twilio": "^5.5.1", + "uuid": "^11.1.0", "winston": "^3.17.0", "winston-cloudwatch": "^6.3.0", "xml2js": "^0.6.2", @@ -70,7 +70,7 @@ "devDependencies": { "@eslint/js": "^9.23.0", "@trivago/prettier-plugin-sort-imports": "^5.2.2", - "concurrently": "^8.2.2", + "concurrently": "^9.1.2", "eslint": "^9.23.0", "eslint-plugin-react": "^7.37.4", "globals": "^15.15.0", @@ -4549,18 +4549,16 @@ } }, "node_modules/concurrently": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", + "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", - "date-fns": "^2.30.0", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", - "spawn-command": "0.0.2", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" @@ -4570,7 +4568,7 @@ "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": "^14.13.0 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" @@ -4829,23 +4827,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -6106,19 +6087,6 @@ "@google-cloud/storage": "^7.14.0" } }, - "node_modules/firebase-admin/node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -9136,12 +9104,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "license": "MIT" - }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -9312,12 +9274,6 @@ "node": ">=0.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" - }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -10132,12 +10088,6 @@ "node": ">=10" } }, - "node_modules/spawn-command": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", - "dev": true - }, "node_modules/specificity": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", @@ -10708,18 +10658,17 @@ "license": "Unlicense" }, "node_modules/twilio": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.23.0.tgz", - "integrity": "sha512-LdNBQfOe0dY2oJH2sAsrxazpgfFQo5yXGxe96QA8UWB5uu+433PrUbkv8gQ5RmrRCqUTPQ0aOrIyAdBr1aB03Q==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.5.1.tgz", + "integrity": "sha512-b1gLd2eMsCSCHRerp3GQUedVlz0nCTt5FbyPxDPmMvk5cm6eIPk4ZTp5JNpgucARZgpCB2uUACJbdcidEHAUBA==", "license": "MIT", "dependencies": { - "axios": "^1.6.0", + "axios": "^1.7.8", "dayjs": "^1.11.9", "https-proxy-agent": "^5.0.0", - "jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.2", "qs": "^6.9.4", "scmp": "^2.1.0", - "url-parse": "^1.5.9", "xmlbuilder": "^13.0.2" }, "engines": { @@ -10911,16 +10860,6 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "license": "MIT" }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10937,16 +10876,16 @@ } }, "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/valid-data-url": { diff --git a/package.json b/package.json index 8245785a7..7567e361b 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,8 @@ "socket.io": "^4.8.1", "socket.io-adapter": "^2.5.5", "ssh2-sftp-client": "^11.0.0", - "twilio": "^4.23.0", - "uuid": "^10.0.0", + "twilio": "^5.5.1", + "uuid": "^11.1.0", "winston": "^3.17.0", "winston-cloudwatch": "^6.3.0", "xml2js": "^0.6.2", @@ -80,7 +80,7 @@ "devDependencies": { "@eslint/js": "^9.23.0", "@trivago/prettier-plugin-sort-imports": "^5.2.2", - "concurrently": "^8.2.2", + "concurrently": "^9.1.2", "eslint": "^9.23.0", "eslint-plugin-react": "^7.37.4", "globals": "^15.15.0", diff --git a/server/accounting/pbs/pbs-ap-allocations.js b/server/accounting/pbs/pbs-ap-allocations.js index a969e82c8..9574b166d 100644 --- a/server/accounting/pbs/pbs-ap-allocations.js +++ b/server/accounting/pbs/pbs-ap-allocations.js @@ -12,7 +12,7 @@ const AxiosLib = require("axios").default; const axios = AxiosLib.create(); const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants"); const { CheckForErrors } = require("./pbs-job-export"); -const uuid = require("uuid").v4; + axios.interceptors.request.use((x) => { const socket = x.socket; @@ -21,6 +21,7 @@ axios.interceptors.request.use((x) => { ...x.headers[x.method], ...x.headers }; + const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${ x.url } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`; diff --git a/server/data/arms.js b/server/data/arms.js index 70ad3be5e..6dadc9364 100644 --- a/server/data/arms.js +++ b/server/data/arms.js @@ -18,7 +18,7 @@ const entegralEndpoint = : "https://uat-ws.armsbusinesssolutions.net/RepairOrderFolderService/RepairOrderFolderService.asmx?WSDL"; const client = require("../graphql-client/graphql-client").client; -const uuid = require("uuid").v4; +const { v4 } = require("uuid"); const momentFormat = "yyyy-MM-DDTHH:mm:ss.SSS"; @@ -79,7 +79,7 @@ exports.default = async (req, res) => { } try { - const transId = uuid(); // Can this actually be the job id? + const transId = v4(); // Can this actually be the job id? let obj = { RqUID: transId, DocumentInfo: { diff --git a/server/routes/miscellaneousRoutes.js b/server/routes/miscellaneousRoutes.js index a3f3ca281..aeb59af93 100644 --- a/server/routes/miscellaneousRoutes.js +++ b/server/routes/miscellaneousRoutes.js @@ -14,7 +14,7 @@ const { taskAssignedEmail, tasksRemindEmail } = require("../email/tasksEmails"); const { canvastest } = require("../render/canvas-handler"); const { alertCheck } = require("../alerts/alertcheck"); const updateBodyshopCache = require("../web-sockets/updateBodyshopCache"); -const uuid = require("uuid").v4; +const { v4 } = require("uuid"); //Test route to ensure Express is responding. router.get("/test", eventAuthorizationMiddleware, async function (req, res) { @@ -83,7 +83,7 @@ router.get("/wstest", eventAuthorizationMiddleware, (req, res) => { // image_path: [], newMessage: { conversation: { - id: uuid(), + id: v4(), archived: false, bodyshop: { id: "bfec8c8c-b7f1-49e0-be4c-524455f4e582", From 922aaaf4b2596701b3a400af2aadb9d38940fc76 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 13:23:41 -0400 Subject: [PATCH 21/33] IO-3183-Dependency-Updates-and-maintenance - Minor front end dep bump, logging clean up on OS --- client/package-lock.json | 24 ++++++++++++------------ client/package.json | 4 ++-- server/opensearch/os-handler.js | 6 ++++++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 8219a9e5b..e579c3317 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -28,7 +28,7 @@ "@vitejs/plugin-react": "^4.3.4", "antd": "^5.24.5", "apollo-link-logger": "^2.0.1", - "apollo-link-sentry": "^4.1.0", + "apollo-link-sentry": "^4.2.0", "autosize": "^6.0.1", "axios": "^1.8.4", "classnames": "^2.5.1", @@ -113,12 +113,12 @@ "vite-plugin-babel": "^1.3.0", "vite-plugin-eslint": "^1.8.1", "vite-plugin-node-polyfills": "^0.23.0", - "vite-plugin-pwa": "^0.21.1", + "vite-plugin-pwa": "^0.21.2", "vite-plugin-style-import": "^2.0.0", "workbox-window": "^7.3.0" }, "engines": { - "node": ">=18.18.2" + "node": ">=22.0.0" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.6.1" @@ -6180,20 +6180,20 @@ } }, "node_modules/apollo-link-sentry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/apollo-link-sentry/-/apollo-link-sentry-4.1.0.tgz", - "integrity": "sha512-wHiJXZ9OzmRbV3famykszz0E0SZyCBpa3Rt0EVrdOKDN9qQQaE63xQB2lFa3mUkZb95GMc+6ugPt8bVIkwsRPQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/apollo-link-sentry/-/apollo-link-sentry-4.2.0.tgz", + "integrity": "sha512-w8EUM4aEw1/VxIB3KOP11T8qz44oWRcbXRd2vJq/qHnfRMKS5HkMerSIYwKN2e8k9H8ubfkwBvStH51CVf4wwg==", "license": "MIT", "dependencies": { "deepmerge": "^4.2.2", - "dot-prop": "^6.0.0", + "dot-prop": "^6", "tslib": "^2.0.3", "zen-observable-ts": "^1.2.5" }, "peerDependencies": { "@apollo/client": "^3.2.3", - "@sentry/core": "^8.33.0", - "graphql": "15 - 16" + "@sentry/core": "^8.33 || ^9", + "graphql": "^15 || ^16" } }, "node_modules/argparse": { @@ -17009,9 +17009,9 @@ } }, "node_modules/vite-plugin-pwa": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.21.1.tgz", - "integrity": "sha512-rkTbKFbd232WdiRJ9R3u+hZmf5SfQljX1b45NF6oLA6DSktEKpYllgTo1l2lkiZWMWV78pABJtFjNXfBef3/3Q==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.21.2.tgz", + "integrity": "sha512-vFhH6Waw8itNu37hWUJxL50q+CBbNcMVzsKaYHQVrfxTt3ihk3PeLO22SbiP1UNWzcEPaTQv+YVxe4G0KOjAkg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/client/package.json b/client/package.json index bd0966e2d..fbb9d218a 100644 --- a/client/package.json +++ b/client/package.json @@ -27,7 +27,7 @@ "@vitejs/plugin-react": "^4.3.4", "antd": "^5.24.5", "apollo-link-logger": "^2.0.1", - "apollo-link-sentry": "^4.1.0", + "apollo-link-sentry": "^4.2.0", "autosize": "^6.0.1", "axios": "^1.8.4", "classnames": "^2.5.1", @@ -147,7 +147,7 @@ "vite-plugin-babel": "^1.3.0", "vite-plugin-eslint": "^1.8.1", "vite-plugin-node-polyfills": "^0.23.0", - "vite-plugin-pwa": "^0.21.1", + "vite-plugin-pwa": "^0.21.2", "vite-plugin-style-import": "^2.0.0", "workbox-window": "^7.3.0" } diff --git a/server/opensearch/os-handler.js b/server/opensearch/os-handler.js index 9fd500a46..48310c4f2 100644 --- a/server/opensearch/os-handler.js +++ b/server/opensearch/os-handler.js @@ -160,6 +160,11 @@ async function OpenSearchUpdateHandler(req, res) { res.status(200).json(response.body); } } catch (error) { + // We don't want this spam message existing in development/test, + if (process.env?.NODE_ENV !== "production" && error?.message === "Invalid URL") { + return res.status(400).json(JSON.stringify(error)); + } + logger.log("os-handler-error", "ERROR", null, null, { id: req.body.event.data.new.id, index: req.body.table.name, @@ -167,6 +172,7 @@ async function OpenSearchUpdateHandler(req, res) { stack: error.stack // body: document }); + res.status(400).json(JSON.stringify(error)); } } From 4589e7fa05302a5ea4327df33d81af9213324a7e Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 13:54:18 -0400 Subject: [PATCH 22/33] IO-3183-Dependency-Updates-and-maintenance - Revert react-grid-layout --- client/package-lock.json | 39 +++++++++++++++------------------------ client/package.json | 2 +- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index e579c3317..03db0e103 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -59,7 +59,7 @@ "react-dom": "^18.3.1", "react-drag-listview": "^2.0.0", "react-grid-gallery": "^1.0.1", - "react-grid-layout": "^1.5.1", + "react-grid-layout": "1.3.4", "react-i18next": "^14.1.3", "react-icons": "^5.5.0", "react-image-lightbox": "^5.1.4", @@ -11233,6 +11233,13 @@ "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -13933,38 +13940,22 @@ } }, "node_modules/react-grid-layout": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.1.tgz", - "integrity": "sha512-4Fr+kKMk0+m1HL/BWfHxi/lRuaOmDNNKQDcu7m12+NEYcen20wIuZFo789u3qWCyvUsNUxCiyf0eKq4WiJSNYw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz", + "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==", "license": "MIT", "dependencies": { - "clsx": "^2.0.0", - "fast-equals": "^4.0.3", + "clsx": "^1.1.1", + "lodash.isequal": "^4.0.0", "prop-types": "^15.8.1", - "react-draggable": "^4.4.5", - "react-resizable": "^3.0.5", - "resize-observer-polyfill": "^1.5.1" + "react-draggable": "^4.0.0", + "react-resizable": "^3.0.4" }, "peerDependencies": { "react": ">= 16.3.0", "react-dom": ">= 16.3.0" } }, - "node_modules/react-grid-layout/node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/react-grid-layout/node_modules/fast-equals": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", - "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", - "license": "MIT" - }, "node_modules/react-i18next": { "version": "14.1.3", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.3.tgz", diff --git a/client/package.json b/client/package.json index fbb9d218a..ac6f0679c 100644 --- a/client/package.json +++ b/client/package.json @@ -58,7 +58,7 @@ "react-dom": "^18.3.1", "react-drag-listview": "^2.0.0", "react-grid-gallery": "^1.0.1", - "react-grid-layout": "^1.5.1", + "react-grid-layout": "1.3.4", "react-i18next": "^14.1.3", "react-icons": "^5.5.0", "react-image-lightbox": "^5.1.4", From c568970fd828d87234278e59d4698347454dbd8e Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 14:05:21 -0400 Subject: [PATCH 23/33] IO-3183-Dependency-Updates-and-maintenance - Refactor dashboard-grid.component.jsx to separate some of the logic into componentList.js and createDashboardQuery.js --- .../dashboard-grid/componentList.js | 132 +++++++++++ .../dashboard-grid/createDashboardQuery.js | 85 +++++++ .../dashboard-grid.component.jsx | 224 +----------------- 3 files changed, 223 insertions(+), 218 deletions(-) create mode 100644 client/src/components/dashboard-grid/componentList.js create mode 100644 client/src/components/dashboard-grid/createDashboardQuery.js diff --git a/client/src/components/dashboard-grid/componentList.js b/client/src/components/dashboard-grid/componentList.js new file mode 100644 index 000000000..015d3509e --- /dev/null +++ b/client/src/components/dashboard-grid/componentList.js @@ -0,0 +1,132 @@ +import i18next from "i18next"; +import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component.jsx"; +import { + DashboardTotalProductionHours, + DashboardTotalProductionHoursGql +} from "../dashboard-components/total-production-hours/total-production-hours.component.jsx"; +import DashboardProjectedMonthlySales, { + DashboardProjectedMonthlySalesGql +} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx"; +import DashboardMonthlyRevenueGraph, { + DashboardMonthlyRevenueGraphGql +} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx"; +import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx"; +import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component.jsx"; +import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx"; +import DashboardMonthlyEmployeeEfficiency, { + DashboardMonthlyEmployeeEfficiencyGql +} from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx"; +import DashboardScheduledInToday, { + DashboardScheduledInTodayGql +} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx"; +import DashboardScheduledOutToday, { + DashboardScheduledOutTodayGql +} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx"; +import JobLifecycleDashboardComponent, { + JobLifecycleDashboardGQL +} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx"; + +const componentList = { + ProductionDollars: { + label: i18next.t("dashboard.titles.productiondollars"), + component: DashboardTotalProductionDollars, + gqlFragment: null, + w: 1, + h: 1, + minW: 2, + minH: 1 + }, + ProductionHours: { + label: i18next.t("dashboard.titles.productionhours"), + component: DashboardTotalProductionHours, + gqlFragment: DashboardTotalProductionHoursGql, + w: 3, + h: 1, + minW: 3, + minH: 1 + }, + ProjectedMonthlySales: { + label: i18next.t("dashboard.titles.projectedmonthlysales"), + component: DashboardProjectedMonthlySales, + gqlFragment: DashboardProjectedMonthlySalesGql, + w: 2, + h: 1, + minW: 2, + minH: 1 + }, + MonthlyRevenueGraph: { + label: i18next.t("dashboard.titles.monthlyrevenuegraph"), + component: DashboardMonthlyRevenueGraph, + gqlFragment: DashboardMonthlyRevenueGraphGql, + w: 4, + h: 2, + minW: 4, + minH: 2 + }, + MonthlyJobCosting: { + label: i18next.t("dashboard.titles.monthlyjobcosting"), + component: DashboardMonthlyJobCosting, + gqlFragment: null, + minW: 6, + minH: 3, + w: 6, + h: 3 + }, + MonthlyPartsSales: { + label: i18next.t("dashboard.titles.monthlypartssales"), + component: DashboardMonthlyPartsSales, + gqlFragment: null, + minW: 2, + minH: 2, + w: 2, + h: 2 + }, + MonthlyLaborSales: { + label: i18next.t("dashboard.titles.monthlylaborsales"), + component: DashboardMonthlyLaborSales, + gqlFragment: null, + minW: 2, + minH: 2, + w: 2, + h: 2 + }, + // Typo in Efficency should be Efficiency, but changing it would reset users dashboard settings + MonthlyEmployeeEfficency: { + label: i18next.t("dashboard.titles.monthlyemployeeefficiency"), + component: DashboardMonthlyEmployeeEfficiency, + gqlFragment: DashboardMonthlyEmployeeEfficiencyGql, + minW: 2, + minH: 2, + w: 2, + h: 2 + }, + ScheduleInToday: { + label: i18next.t("dashboard.titles.scheduledintoday"), + component: DashboardScheduledInToday, + gqlFragment: DashboardScheduledInTodayGql, + minW: 6, + minH: 2, + w: 10, + h: 3 + }, + ScheduleOutToday: { + label: i18next.t("dashboard.titles.scheduledouttoday"), + component: DashboardScheduledOutToday, + gqlFragment: DashboardScheduledOutTodayGql, + minW: 6, + minH: 2, + w: 10, + h: 3 + }, + JobLifecycle: { + label: i18next.t("dashboard.titles.joblifecycle"), + component: JobLifecycleDashboardComponent, + gqlFragment: JobLifecycleDashboardGQL, + minW: 6, + minH: 3, + w: 6, + h: 3 + } +}; + +export default componentList; diff --git a/client/src/components/dashboard-grid/createDashboardQuery.js b/client/src/components/dashboard-grid/createDashboardQuery.js new file mode 100644 index 000000000..a37f4aa4a --- /dev/null +++ b/client/src/components/dashboard-grid/createDashboardQuery.js @@ -0,0 +1,85 @@ +import { gql } from "@apollo/client"; +import dayjs from "../../utils/day.js"; +import componentList from "./componentList.js"; + +const createDashboardQuery = (state) => { + const componentBasedAdditions = + state && + Array.isArray(state.layout) && + state.layout.map((item, index) => componentList[item.i].gqlFragment || "").join(""); + return gql` + query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""} + monthly_sales: jobs(where: {_and: [ + { voided: {_eq: false}}, + {date_invoiced: {_gte: "${dayjs() + .startOf("month") + .startOf("day") + .toISOString()}"}}, {date_invoiced: {_lte: "${dayjs().endOf("month").endOf("day").toISOString()}"}}]}) { + id + ro_number + date_invoiced + job_totals + rate_la1 + rate_la2 + rate_la3 + rate_la4 + rate_laa + rate_lab + rate_lad + rate_lae + rate_laf + rate_lag + rate_lam + rate_lar + rate_las + rate_lau + rate_ma2s + rate_ma2t + rate_ma3s + rate_mabl + rate_macs + rate_mahw + rate_mapa + rate_mash + rate_matd + joblines(where: { removed: { _eq: false } }) { + id + mod_lbr_ty + mod_lb_hrs + act_price + part_qty + part_type + } + } + production_jobs: jobs(where: { inproduction: { _eq: true } }) { + id + ro_number + ins_co_nm + job_totals + joblines(where: { removed: { _eq: false } }) { + id + mod_lbr_ty + mod_lb_hrs + act_price + part_qty + part_type + } + labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + } + }`; +}; + +export default createDashboardQuery; diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index 6c3412a3d..83608e830 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -1,10 +1,8 @@ import Icon, { SyncOutlined } from "@ant-design/icons"; -import { gql, useMutation, useQuery } from "@apollo/client"; +import { useMutation, useQuery } from "@apollo/client"; import { Button, Dropdown, Space } from "antd"; import { PageHeader } from "@ant-design/pro-layout"; -import i18next from "i18next"; import _ from "lodash"; -import dayjs from "../../utils/day"; import React, { useState } from "react"; import { Responsive, WidthProvider } from "react-grid-layout"; import { useTranslation } from "react-i18next"; @@ -15,38 +13,13 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AlertComponent from "../alert/alert.component"; -import DashboardMonthlyEmployeeEfficiency, { - DashboardMonthlyEmployeeEfficiencyGql -} from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component"; -import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component"; -import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component"; -import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component"; -import DashboardMonthlyRevenueGraph, { - DashboardMonthlyRevenueGraphGql -} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component"; -import DashboardProjectedMonthlySales, { - DashboardProjectedMonthlySalesGql -} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component"; -import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component"; -import DashboardTotalProductionHours, { - DashboardTotalProductionHoursGql -} from "../dashboard-components/total-production-hours/total-production-hours.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; -//Combination of the following: -// /node_modules/react-grid-layout/css/styles.css -// /node_modules/react-resizable/css/styles.css -import DashboardScheduledInToday, { - DashboardScheduledInTodayGql -} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component"; -import DashboardScheduledOutToday, { - DashboardScheduledOutTodayGql -} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component"; -import JobLifecycleDashboardComponent, { - JobLifecycleDashboardGQL -} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component"; -import "./dashboard-grid.styles.scss"; import { GenerateDashboardData } from "./dashboard-grid.utils"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; +import componentList from "./componentList.js"; +import createDashboardQuery from "./createDashboardQuery.js"; + +import "./dashboard-grid.styles.scss"; const ResponsiveReactGridLayout = WidthProvider(Responsive); @@ -54,6 +27,7 @@ const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, bodyshop: selectBodyshop }); + const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); @@ -193,189 +167,3 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { } export default connect(mapStateToProps, mapDispatchToProps)(DashboardGridComponent); - -const componentList = { - ProductionDollars: { - label: i18next.t("dashboard.titles.productiondollars"), - component: DashboardTotalProductionDollars, - gqlFragment: null, - w: 1, - h: 1, - minW: 2, - minH: 1 - }, - ProductionHours: { - label: i18next.t("dashboard.titles.productionhours"), - component: DashboardTotalProductionHours, - gqlFragment: DashboardTotalProductionHoursGql, - w: 3, - h: 1, - minW: 3, - minH: 1 - }, - ProjectedMonthlySales: { - label: i18next.t("dashboard.titles.projectedmonthlysales"), - component: DashboardProjectedMonthlySales, - gqlFragment: DashboardProjectedMonthlySalesGql, - w: 2, - h: 1, - minW: 2, - minH: 1 - }, - MonthlyRevenueGraph: { - label: i18next.t("dashboard.titles.monthlyrevenuegraph"), - component: DashboardMonthlyRevenueGraph, - gqlFragment: DashboardMonthlyRevenueGraphGql, - w: 4, - h: 2, - minW: 4, - minH: 2 - }, - MonthlyJobCosting: { - label: i18next.t("dashboard.titles.monthlyjobcosting"), - component: DashboardMonthlyJobCosting, - gqlFragment: null, - minW: 6, - minH: 3, - w: 6, - h: 3 - }, - MonthlyPartsSales: { - label: i18next.t("dashboard.titles.monthlypartssales"), - component: DashboardMonthlyPartsSales, - gqlFragment: null, - minW: 2, - minH: 2, - w: 2, - h: 2 - }, - MonthlyLaborSales: { - label: i18next.t("dashboard.titles.monthlylaborsales"), - component: DashboardMonthlyLaborSales, - gqlFragment: null, - minW: 2, - minH: 2, - w: 2, - h: 2 - }, - // Typo in Efficency should be Efficiency, but changing it would reset users dashboard settings - MonthlyEmployeeEfficency: { - label: i18next.t("dashboard.titles.monthlyemployeeefficiency"), - component: DashboardMonthlyEmployeeEfficiency, - gqlFragment: DashboardMonthlyEmployeeEfficiencyGql, - minW: 2, - minH: 2, - w: 2, - h: 2 - }, - ScheduleInToday: { - label: i18next.t("dashboard.titles.scheduledintoday"), - component: DashboardScheduledInToday, - gqlFragment: DashboardScheduledInTodayGql, - minW: 6, - minH: 2, - w: 10, - h: 3 - }, - ScheduleOutToday: { - label: i18next.t("dashboard.titles.scheduledouttoday"), - component: DashboardScheduledOutToday, - gqlFragment: DashboardScheduledOutTodayGql, - minW: 6, - minH: 2, - w: 10, - h: 3 - }, - JobLifecycle: { - label: i18next.t("dashboard.titles.joblifecycle"), - component: JobLifecycleDashboardComponent, - gqlFragment: JobLifecycleDashboardGQL, - minW: 6, - minH: 3, - w: 6, - h: 3 - } -}; - -const createDashboardQuery = (state) => { - const componentBasedAdditions = - state && - Array.isArray(state.layout) && - state.layout.map((item, index) => componentList[item.i].gqlFragment || "").join(""); - return gql` - query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""} - monthly_sales: jobs(where: {_and: [ - { voided: {_eq: false}}, - {date_invoiced: {_gte: "${dayjs() - .startOf("month") - .startOf("day") - .toISOString()}"}}, {date_invoiced: {_lte: "${dayjs() - .endOf("month") - .endOf("day") - .toISOString()}"}}]}) { - id - ro_number - date_invoiced - job_totals - rate_la1 - rate_la2 - rate_la3 - rate_la4 - rate_laa - rate_lab - rate_lad - rate_lae - rate_laf - rate_lag - rate_lam - rate_lar - rate_las - rate_lau - rate_ma2s - rate_ma2t - rate_ma3s - rate_mabl - rate_macs - rate_mahw - rate_mapa - rate_mash - rate_matd - joblines(where: { removed: { _eq: false } }) { - id - mod_lbr_ty - mod_lb_hrs - act_price - part_qty - part_type - } - } - production_jobs: jobs(where: { inproduction: { _eq: true } }) { - id - ro_number - ins_co_nm - job_totals - joblines(where: { removed: { _eq: false } }) { - id - mod_lbr_ty - mod_lb_hrs - act_price - part_qty - part_type - } - labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { - aggregate { - sum { - mod_lb_hrs - } - } - } - } - }`; -}; From 3b21c603f6a068661c4cb7cbf752924dd21b13e8 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 14:09:21 -0400 Subject: [PATCH 24/33] IO-3183-Dependency-Updates-and-maintenance - Cleaning dashboard-grid.component.jsx --- .../dashboard-grid/dashboard-grid.component.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index 83608e830..1fd97ceb1 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -2,8 +2,7 @@ import Icon, { SyncOutlined } from "@ant-design/icons"; import { useMutation, useQuery } from "@apollo/client"; import { Button, Dropdown, Space } from "antd"; import { PageHeader } from "@ant-design/pro-layout"; -import _ from "lodash"; -import React, { useState } from "react"; +import { useMemo, useState } from "react"; import { Responsive, WidthProvider } from "react-grid-layout"; import { useTranslation } from "react-i18next"; import { MdClose } from "react-icons/md"; @@ -20,6 +19,7 @@ import componentList from "./componentList.js"; import createDashboardQuery from "./createDashboardQuery.js"; import "./dashboard-grid.styles.scss"; +import cloneDeep from "lodash/cloneDeep"; const ResponsiveReactGridLayout = WidthProvider(Responsive); @@ -71,7 +71,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { logImEXEvent("dashboard_remove_component", { name: key }); const idxToRemove = state.items.findIndex((i) => i.i === key); - const items = _.cloneDeep(state.items); + const items = cloneDeep(state.items); items.splice(idxToRemove, 1); setState({ ...state, items }); @@ -94,7 +94,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { }); }; - const dashboarddata = React.useMemo(() => GenerateDashboardData(data), [data]); + const dashboardData = useMemo(() => GenerateDashboardData(data), [data]); const existingLayoutKeys = state.items.map((i) => i.i); const menuItems = Object.keys(componentList).map((key) => ({ @@ -156,7 +156,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { }} onClick={() => handleRemoveComponent(item.i)} /> - +
); From e36bb65e4c6cf661d5637dfce2713a2203fab02f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 14:14:24 -0400 Subject: [PATCH 25/33] IO-3183-Dependency-Updates-and-maintenance - Cleaning dashboard-grid.component.jsx --- .../components/dashboard-grid/dashboard-grid.component.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index 1fd97ceb1..70800158a 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -1,4 +1,5 @@ import Icon, { SyncOutlined } from "@ant-design/icons"; +import { isEmpty, cloneDeep } from "lodash"; import { useMutation, useQuery } from "@apollo/client"; import { Button, Dropdown, Space } from "antd"; import { PageHeader } from "@ant-design/pro-layout"; @@ -19,7 +20,6 @@ import componentList from "./componentList.js"; import createDashboardQuery from "./createDashboardQuery.js"; import "./dashboard-grid.styles.scss"; -import cloneDeep from "lodash/cloneDeep"; const ResponsiveReactGridLayout = WidthProvider(Responsive); @@ -59,8 +59,8 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { layout: { ...state, layout, layouts } } }); - if (!!result.errors) { - notification["error"]({ + if (!isEmpty(result?.errors)) { + notification.error({ message: t("dashboard.errors.updatinglayout", { message: JSON.stringify(result.errors) }) From b5611c8470f9bc66a67468e8fa725dc6e2400a51 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 14:16:25 -0400 Subject: [PATCH 26/33] IO-3183-Dependency-Updates-and-maintenance - Cleaning dashboard-grid.component.jsx --- .../components/dashboard-grid/dashboard-grid.component.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index 70800158a..082e404ac 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -59,6 +59,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { layout: { ...state, layout, layouts } } }); + if (!isEmpty(result?.errors)) { notification.error({ message: t("dashboard.errors.updatinglayout", { @@ -67,6 +68,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { }); } }; + const handleRemoveComponent = (key) => { logImEXEvent("dashboard_remove_component", { name: key }); const idxToRemove = state.items.findIndex((i) => i.i === key); @@ -95,6 +97,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { }; const dashboardData = useMemo(() => GenerateDashboardData(data), [data]); + const existingLayoutKeys = state.items.map((i) => i.i); const menuItems = Object.keys(componentList).map((key) => ({ @@ -130,7 +133,6 @@ export function DashboardGridComponent({ currentUser, bodyshop }) { width="100%" layouts={state.layouts} onLayoutChange={handleLayoutChange} - // onBreakpointChange={onBreakpointChange} > {state.items.map((item, index) => { const TheComponent = componentList[item.i].component; From c9cc9d2df38d1262009cdbdc511371e85d33abfe Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 14:26:32 -0400 Subject: [PATCH 27/33] IO-3183-Dependency-Updates-and-maintenance - Cleaning dashboard-grid.component.jsx --- server/routes/smsRoutes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/routes/smsRoutes.js b/server/routes/smsRoutes.js index bb23d24e8..1b169747d 100644 --- a/server/routes/smsRoutes.js +++ b/server/routes/smsRoutes.js @@ -7,6 +7,7 @@ const { status, markConversationRead } = require("../sms/status"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); // Twilio Webhook Middleware for production +// TODO: Look into this because it technically is never validating anything const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }); router.post("/receive", twilioWebhookMiddleware, receive); From aaf966e721bc6635a471c8f3d97843272467e3ce Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 15:01:18 -0400 Subject: [PATCH 28/33] IO-3183-Dependency-Updates-and-maintenance - remove userpilot, update i18next , logrocket, react-cookie, react-i18next, react-markdown --- client/package-lock.json | 267 ++++++++++++++++++------------- client/package.json | 13 +- client/src/App/App.container.jsx | 6 - 3 files changed, 159 insertions(+), 127 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 03db0e103..66e922267 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -40,11 +40,11 @@ "env-cmd": "^10.1.0", "exifr": "^7.1.3", "graphql": "^16.10.0", - "i18next": "^23.15.1", + "i18next": "^24.2.3", "i18next-browser-languagedetector": "^8.0.4", "immutability-helper": "^3.1.1", "libphonenumber-js": "^1.12.6", - "logrocket": "^8.1.2", + "logrocket": "^9.0.2", "markerjs2": "^2.32.4", "memoize-one": "^6.0.0", "normalize-url": "^8.0.1", @@ -55,15 +55,15 @@ "react": "^18.3.1", "react-big-calendar": "^1.18.0", "react-color": "^2.19.3", - "react-cookie": "^7.2.2", + "react-cookie": "^8.0.1", "react-dom": "^18.3.1", "react-drag-listview": "^2.0.0", "react-grid-gallery": "^1.0.1", "react-grid-layout": "1.3.4", - "react-i18next": "^14.1.3", + "react-i18next": "^15.4.1", "react-icons": "^5.5.0", "react-image-lightbox": "^5.1.4", - "react-markdown": "^9.0.3", + "react-markdown": "^10.1.0", "react-number-format": "^5.4.3", "react-popopo": "^2.1.9", "react-product-fruits": "^2.2.61", @@ -84,12 +84,11 @@ "styled-components": "^6.1.16", "subscriptions-transport-ws": "^0.11.0", "use-memo-one": "^1.1.3", - "userpilot": "^1.3.9", "vite-plugin-ejs": "^1.7.0", "web-vitals": "^3.5.2" }, "devDependencies": { - "@ant-design/icons": "^5.6.1", + "@ant-design/icons": "^6.0.0", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.26.3", "@dotenvx/dotenvx": "^1.39.0", @@ -193,16 +192,16 @@ } }, "node_modules/@ant-design/icons": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", - "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.0.0.tgz", + "integrity": "sha512-o0aCCAlHc1o4CQcapAwWzHeaW2x9F49g7P3IDtvtNXgHowtRWYb7kiubt8sQPFvfVIVU/jLw2hzeSlNt0FU+Uw==", + "dev": true, "license": "MIT", "dependencies": { - "@ant-design/colors": "^7.0.0", + "@ant-design/colors": "^8.0.0", "@ant-design/icons-svg": "^4.4.0", - "@babel/runtime": "^7.24.8", - "classnames": "^2.2.6", - "rc-util": "^5.31.1" + "@rc-component/util": "^1.2.1", + "classnames": "^2.2.6" }, "engines": { "node": ">=8" @@ -218,6 +217,26 @@ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", "license": "MIT" }, + "node_modules/@ant-design/icons/node_modules/@ant-design/colors": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.0.tgz", + "integrity": "sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/icons/node_modules/@ant-design/fast-color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.0.tgz", + "integrity": "sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, "node_modules/@ant-design/pro-layout": { "version": "7.22.3", "resolved": "https://registry.npmjs.org/@ant-design/pro-layout/-/pro-layout-7.22.3.tgz", @@ -246,6 +265,26 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-layout/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-provider": { "version": "2.15.3", "resolved": "https://registry.npmjs.org/@ant-design/pro-provider/-/pro-provider-2.15.3.tgz", @@ -288,6 +327,26 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-utils/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/react-slick": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", @@ -2279,9 +2338,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz", - "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -3479,36 +3538,6 @@ "integrity": "sha512-AAeTkqyVJGdWLCA60aHDrh3s8h9z8TokyoR1tCpNtYatfe2cdocVdB0AaNquWTmddRWgAklmOBrowsMFQFY8hg==", "license": "MIT" }, - "node_modules/@ndhoule/each": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@ndhoule/each/-/each-2.0.1.tgz", - "integrity": "sha512-wHuJw6x+rF6Q9Skgra++KccjBozCr9ymtna0FhxmV/8xT/hZ2ExGYR8SV8prg8x4AH/7mzDYErNGIVHuzHeybw==", - "license": "MIT", - "dependencies": { - "@ndhoule/keys": "^2.0.0" - } - }, - "node_modules/@ndhoule/includes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@ndhoule/includes/-/includes-2.0.1.tgz", - "integrity": "sha512-Q8zN6f3yIhxgBwZ5ldLozHqJlc/fRQ5+hFFsPMFeC9SJvz0nq8vG9hoRXL1c1iaNFQd7yAZIy2igQpERoFqxqg==", - "license": "MIT", - "dependencies": { - "@ndhoule/each": "^2.0.1" - } - }, - "node_modules/@ndhoule/keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ndhoule/keys/-/keys-2.0.0.tgz", - "integrity": "sha512-vtCqKBC1Av6dsBA8xpAO+cgk051nfaI+PnmTZep2Px0vYrDvpUmLxv7z40COlWH5yCpu3gzNhepk+02yiQiZNw==", - "license": "MIT" - }, - "node_modules/@ndhoule/pick": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ndhoule/pick/-/pick-2.0.0.tgz", - "integrity": "sha512-xkYtpf1pRd8egwvl5tJcdGu+GBd6ZZH3S/zoIQ9txEI+pHF9oTIlxMC9G4CB3sRugAeLgu8qYJGl3tnxWq74Qw==", - "license": "MIT" - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -4142,6 +4171,27 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@rc-component/util": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.2.1.tgz", + "integrity": "sha512-AUVu6jO+lWjQnUOOECwu8iR0EdElQgWW5NBv5vP/Uf9dWbAX3udhMutRlkVXjuac2E40ghkFy+ve00mc/3Fymg==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@redux-saga/core": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.3.0.tgz", @@ -5412,12 +5462,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT" - }, "node_modules/@types/d3-array": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", @@ -6141,6 +6185,26 @@ "react-dom": ">=16.9.0" } }, + "node_modules/antd/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -7324,11 +7388,6 @@ "node": ">=4.0.0" } }, - "node_modules/component-indexof": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", - "integrity": "sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==" - }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", @@ -7388,12 +7447,12 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" } }, "node_modules/copy-to-clipboard": { @@ -10056,9 +10115,9 @@ } }, "node_modules/i18next": { - "version": "23.16.8", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", - "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "version": "24.2.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz", + "integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==", "funding": [ { "type": "individual", @@ -10075,7 +10134,15 @@ ], "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.2" + "@babel/runtime": "^7.26.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/i18next-browser-languagedetector": { @@ -10255,15 +10322,6 @@ "url": "https://opencollective.com/ioredis" } }, - "node_modules/is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -11255,9 +11313,9 @@ "license": "MIT" }, "node_modules/logrocket": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/logrocket/-/logrocket-8.1.3.tgz", - "integrity": "sha512-lWuFhkWYMtARJ2BZUrxP54Dtn3TskpLZ0kDhp14BkrapFQihcN0GB8pvy2vP1OdRFFVh3pAG7AWo/BUbQ9SIbg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/logrocket/-/logrocket-9.0.2.tgz", + "integrity": "sha512-DscsCEi9PLEEXPXWOsf+BGsmwZGIFzn9Ixo+lTezFpR2o3xzZxD7Qz3FWecYYuci6SKvalVciy6msVvMjD16PA==", "license": "MIT" }, "node_modules/long": { @@ -12348,12 +12406,6 @@ "node": ">=8" } }, - "node_modules/obj-case": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/obj-case/-/obj-case-0.2.1.tgz", - "integrity": "sha512-PquYBBTy+Y6Ob/O2574XHhDtHJlV1cJHMCgW+rDRc9J5hhmRelJB3k5dTK/3cVmFVtzvAKuENeuLpoyTzMzkOg==", - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -13873,14 +13925,14 @@ } }, "node_modules/react-cookie": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.2.2.tgz", - "integrity": "sha512-e+hi6axHcw9VODoeVu8WyMWyoosa1pzpyjfvrLdF7CexfU+WSGZdDuRfHa4RJgTpfv3ZjdIpHE14HpYBieHFhg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-8.0.1.tgz", + "integrity": "sha512-QNdAd0MLuAiDiLcDU/2s/eyKmmfMHtjPUKJ2dZ/5CcQ9QKUium4B3o61/haq6PQl/YWFqC5PO8GvxeHKhy3GFA==", "license": "MIT", "dependencies": { - "@types/hoist-non-react-statics": "^3.3.5", + "@types/hoist-non-react-statics": "^3.3.6", "hoist-non-react-statics": "^3.3.2", - "universal-cookie": "^7.0.0" + "universal-cookie": "^8.0.0" }, "peerDependencies": { "react": ">= 16.3.0" @@ -13957,12 +14009,12 @@ } }, "node_modules/react-i18next": { - "version": "14.1.3", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.3.tgz", - "integrity": "sha512-wZnpfunU6UIAiJ+bxwOiTmBOAaB14ha97MjOEnLGac2RJ+h/maIYXZuTHlmyqQVX1UVHmU1YDTQ5vxLmwfXTjw==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.1.tgz", + "integrity": "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.9", + "@babel/runtime": "^7.25.0", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { @@ -14015,12 +14067,13 @@ "license": "MIT" }, "node_modules/react-markdown": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.3.tgz", - "integrity": "sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", @@ -16546,13 +16599,12 @@ } }, "node_modules/universal-cookie": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz", - "integrity": "sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-8.0.1.tgz", + "integrity": "sha512-B6ks9FLLnP1UbPPcveOidfvB9pHjP+wekP2uRYB9YDfKVpvcjKgy1W5Zj+cEXJ9KTPnqOKGfVDQBmn8/YCQfRg==", "license": "MIT", "dependencies": { - "@types/cookie": "^0.6.0", - "cookie": "^0.7.2" + "cookie": "^1.0.2" } }, "node_modules/universalify": { @@ -16757,19 +16809,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/userpilot": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/userpilot/-/userpilot-1.3.9.tgz", - "integrity": "sha512-V0QIuIlAJPB8s3j+qtv7BW7NKSXthlZWuowIu+IZOMGLgUbqQTaSW5m1Ct4wJviPKUNOi8kbhCXN4c4b3zcJzg==", - "license": "MIT", - "dependencies": { - "@ndhoule/includes": "^2.0.1", - "@ndhoule/pick": "^2.0.0", - "component-indexof": "0.0.3", - "is": "^3.1.0", - "obj-case": "^0.2.0" - } - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", diff --git a/client/package.json b/client/package.json index ac6f0679c..30d26ffc7 100644 --- a/client/package.json +++ b/client/package.json @@ -39,11 +39,11 @@ "env-cmd": "^10.1.0", "exifr": "^7.1.3", "graphql": "^16.10.0", - "i18next": "^23.15.1", + "i18next": "^24.2.3", "i18next-browser-languagedetector": "^8.0.4", "immutability-helper": "^3.1.1", "libphonenumber-js": "^1.12.6", - "logrocket": "^8.1.2", + "logrocket": "^9.0.2", "markerjs2": "^2.32.4", "memoize-one": "^6.0.0", "normalize-url": "^8.0.1", @@ -54,15 +54,15 @@ "react": "^18.3.1", "react-big-calendar": "^1.18.0", "react-color": "^2.19.3", - "react-cookie": "^7.2.2", + "react-cookie": "^8.0.1", "react-dom": "^18.3.1", "react-drag-listview": "^2.0.0", "react-grid-gallery": "^1.0.1", "react-grid-layout": "1.3.4", - "react-i18next": "^14.1.3", + "react-i18next": "^15.4.1", "react-icons": "^5.5.0", "react-image-lightbox": "^5.1.4", - "react-markdown": "^9.0.3", + "react-markdown": "^10.1.0", "react-number-format": "^5.4.3", "react-popopo": "^2.1.9", "react-product-fruits": "^2.2.61", @@ -83,7 +83,6 @@ "styled-components": "^6.1.16", "subscriptions-transport-ws": "^0.11.0", "use-memo-one": "^1.1.3", - "userpilot": "^1.3.9", "vite-plugin-ejs": "^1.7.0", "web-vitals": "^3.5.2" }, @@ -123,7 +122,7 @@ "@rollup/rollup-linux-x64-gnu": "4.6.1" }, "devDependencies": { - "@ant-design/icons": "^5.6.1", + "@ant-design/icons": "^6.0.0", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.26.3", "@dotenvx/dotenvx": "^1.39.0", diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index 0f3559d2c..67fd0f444 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -10,12 +10,6 @@ import client from "../utils/GraphQLClient"; import App from "./App"; import * as Sentry from "@sentry/react"; import themeProvider from "./themeProvider"; -import { Userpilot } from "userpilot"; - -// Initialize Userpilot -if (import.meta.env.DEV) { - Userpilot.initialize("NX-69145f08"); -} // Base Split configuration const config = { From 1b5ae29078c56b5cace16a3743269f7004f3cf41 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 24 Mar 2025 15:34:32 -0400 Subject: [PATCH 29/33] IO-3183-Dependency-Updates-and-maintenance - Remove concurrently, clean up scripts in both package.json`s --- client/package.json | 1 - package-lock.json | 71 +++------------------------------------------ package.json | 7 ----- 3 files changed, 4 insertions(+), 75 deletions(-) diff --git a/client/package.json b/client/package.json index 30d26ffc7..f7e354160 100644 --- a/client/package.json +++ b/client/package.json @@ -99,7 +99,6 @@ "build:test:rome": "env-cmd -f .env.test.rome npm run build", "build:production:imex": "env-cmd -f .env.production.imex npm run build", "build:production:rome": "env-cmd -f .env.production.rome npm run build", - "eject": "react-scripts eject", "madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .", "eulaize": "node src/utils/eulaize.js" }, diff --git a/package-lock.json b/package-lock.json index 0db4ef983..76296e97b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,7 +70,6 @@ "devDependencies": { "@eslint/js": "^9.23.0", "@trivago/prettier-plugin-sort-imports": "^5.2.2", - "concurrently": "^9.1.2", "eslint": "^9.23.0", "eslint-plugin-react": "^7.37.4", "globals": "^15.15.0", @@ -4308,8 +4307,8 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -4323,8 +4322,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4548,32 +4547,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/concurrently": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", - "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -9363,16 +9336,6 @@ "integrity": "sha512-c5ouQkOvGHF1qomUUDJGFcXsomeSO2gbEs6hVhMAtlkE1CuaZase/WzoaKFG/EZQuNmq6pw/EMCeEnDvOgCJYQ==", "license": "MIT" }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -10433,22 +10396,6 @@ "pick-util": "^1.1.5" } }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -10608,16 +10555,6 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -11526,8 +11463,8 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -11545,8 +11482,8 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "devOptional": true, "license": "ISC", + "optional": true, "engines": { "node": ">=12" } diff --git a/package.json b/package.json index 7567e361b..4de2904c0 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,6 @@ "scripts": { "setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i", "setup:win": "rimraf node_modules && npm i && cd client && rimraf node_modules && npm i", - "admin": "cd admin && npm start", - "client": "cd client && npm start", - "server": "nodemon server.js", - "build": "cd client && npm run build", - "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"", - "deva": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\" \"npm run admin\"", "start": "node server.js", "makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"" }, @@ -80,7 +74,6 @@ "devDependencies": { "@eslint/js": "^9.23.0", "@trivago/prettier-plugin-sort-imports": "^5.2.2", - "concurrently": "^9.1.2", "eslint": "^9.23.0", "eslint-plugin-react": "^7.37.4", "globals": "^15.15.0", From f485951a4c4f39043d77d47a48fa894ad943d22d Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 24 Mar 2025 12:47:51 -0700 Subject: [PATCH 30/33] IO-3178 Requested Changes for Flat Rate ATS Signed-off-by: Allan Carr --- server/job/job-totals-USA.js | 9 +++++---- server/job/job-totals.js | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/server/job/job-totals-USA.js b/server/job/job-totals-USA.js index 6835dc7cb..dd850dc9b 100644 --- a/server/job/job-totals-USA.js +++ b/server/job/job-totals-USA.js @@ -139,7 +139,7 @@ async function Totals(req, res) { const logger = req.logger; const client = req.userGraphQLClient; - logger.log("job-totals-USA", "DEBUG", req.user.email, job.id, { + logger.log("job-totals-ssu-USA", "DEBUG", req.user.email, job.id, { jobid: job.id, id: id }); @@ -156,7 +156,7 @@ async function Totals(req, res) { res.status(200).json(ret); } catch (error) { - logger.log("job-totals-USA-error", "ERROR", req.user.email, job.id, { + logger.log("job-totals-ssu-USA-error", "ERROR", req.user.email, job.id, { jobid: job.id, error: error.message, stack: error.stack @@ -258,6 +258,8 @@ async function AtsAdjustmentsIfRequired({ job, client, user }) { logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, { jobid: job.id, atsLineIndex: atsLineIndex, + atsAmount: atsAmount, + jobline: job.joblines[atsLineIndex], error: error.message, stack: error.stack }); @@ -1189,9 +1191,8 @@ function CalculateTaxesTotals(job, otherTotals) { exports.default = Totals; +//eslint-disable-next-line no-unused-vars function DiscountNotAlreadyCounted(jobline, joblines) { - void jobline; - void joblines; return false; } diff --git a/server/job/job-totals.js b/server/job/job-totals.js index 0a160426d..6182f9ced 100644 --- a/server/job/job-totals.js +++ b/server/job/job-totals.js @@ -87,7 +87,7 @@ async function Totals(req, res) { const logger = req.logger; const client = req.userGraphQLClient; - logger.log("job-totals-", "DEBUG", req.user.email, job.id, { + logger.log("job-totals-ssu", "DEBUG", req.user.email, job.id, { jobid: job.id, id: id }); @@ -104,7 +104,7 @@ async function Totals(req, res) { res.status(200).json(ret); } catch (error) { - logger.log("job-totals-error", "ERROR", req.user.email, job.id, { + logger.log("job-totals-ssu-error", "ERROR", req.user.email, job.id, { jobid: job.id, error: error.message, stack: error.stack @@ -206,6 +206,8 @@ async function AtsAdjustmentsIfRequired({ job, client, user }) { logger.log("job-totals-ssu-ats-error", "ERROR", user?.email, job.id, { jobid: job.id, atsLineIndex: atsLineIndex, + atsAmount: atsAmount, + jobline: job.joblines[atsLineIndex], error: error.message, stack: error.stack }); From b88795078cc120eba94ab277b739cf418b9dd5e2 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Mon, 24 Mar 2025 18:16:31 -0700 Subject: [PATCH 31/33] IO-3185 Job Drawer Suspend Job Signed-off-by: Allan Carr --- .../job-detail-cards.component.jsx | 53 ++++++++++++++---- .../production-list-detail.component.jsx | 54 +++++++++++++++---- 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/client/src/components/job-detail-cards/job-detail-cards.component.jsx b/client/src/components/job-detail-cards/job-detail-cards.component.jsx index ea66c9112..5e5df5b58 100644 --- a/client/src/components/job-detail-cards/job-detail-cards.component.jsx +++ b/client/src/components/job-detail-cards/job-detail-cards.component.jsx @@ -1,17 +1,21 @@ -import { PrinterFilled } from "@ant-design/icons"; -import { useQuery } from "@apollo/client"; +import { PauseCircleOutlined, PlayCircleOutlined, PrinterFilled } from "@ant-design/icons"; +import { useMutation, useQuery } from "@apollo/client"; import { Button, Card, Col, Divider, Drawer, Grid, Row, Space } from "antd"; import queryString from "query-string"; -import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { createStructuredSelector } from "reselect"; -import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { QUERY_JOB_CARD_DETAILS, UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions.js"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings.js"; import AlertComponent from "../alert/alert.component"; import JobSyncButton from "../job-sync-button/job-sync-button.component"; +import JobWatcherToggleContainer from "../job-watcher-toggle/job-watcher-toggle.container.jsx"; import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import JobDetailCardsDamageComponent from "./job-detail-cards.damage.component"; @@ -21,15 +25,21 @@ import JobDetailCardsInsuranceComponent from "./job-detail-cards.insurance.compo import JobDetailCardsNotesComponent from "./job-detail-cards.notes.component"; import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component"; import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component"; -import JobWatcherToggleContainer from "../job-watcher-toggle/job-watcher-toggle.container.jsx"; -import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ - setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })) + setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })), + insertAuditTrail: ({ jobid, operation, type }) => + dispatch( + insertAuditTrail({ + jobid, + operation, + type + }) + ) }); const span = { @@ -38,8 +48,9 @@ const span = { xxl: { span: 8 } }; -export function JobDetailCards({ bodyshop, setPrintCenterContext }) { +export function JobDetailCards({ bodyshop, setPrintCenterContext, insertAuditTrail }) { const { scenarioNotificationsOn } = useSocket(); + const [updateJob] = useMutation(UPDATE_JOB); const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) .filter((screen) => !!screen[1]) .slice(-1)[0]; @@ -91,7 +102,29 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) { extra={ - + diff --git a/client/src/components/production-list-detail/production-list-detail.component.jsx b/client/src/components/production-list-detail/production-list-detail.component.jsx index ea2d6ef67..7db55bd07 100644 --- a/client/src/components/production-list-detail/production-list-detail.component.jsx +++ b/client/src/components/production-list-detail/production-list-detail.component.jsx @@ -1,17 +1,20 @@ -import { PrinterFilled } from "@ant-design/icons"; -import { useQuery } from "@apollo/client"; -import { Button, Descriptions, Drawer, Space } from "antd"; +import { PauseCircleOutlined, PlayCircleOutlined, PrinterFilled } from "@ant-design/icons"; import { PageHeader } from "@ant-design/pro-layout"; +import { useMutation, useQuery } from "@apollo/client"; +import { Button, Descriptions, Drawer, Space } from "antd"; import queryString from "query-string"; -import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; import { createStructuredSelector } from "reselect"; -import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries"; +import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; +import { logImEXEvent } from "../../firebase/firebase.utils.js"; +import { QUERY_JOB_CARD_DETAILS, UPDATE_JOB } from "../../graphql/jobs.queries"; +import { insertAuditTrail } from "../../redux/application/application.actions.js"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import AuditTrailMapping from "../../utils/AuditTrailMappings.js"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { DateFormatter } from "../../utils/DateFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter"; @@ -24,22 +27,29 @@ import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.p import CardTemplate from "../job-detail-cards/job-detail-cards.template.component"; import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container"; import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component"; +import JobWatcherToggleContainer from "../job-watcher-toggle/job-watcher-toggle.container.jsx"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import ProductionRemoveButton from "../production-remove-button/production-remove-button.component"; -import JobWatcherToggleContainer from "../job-watcher-toggle/job-watcher-toggle.container.jsx"; -import { useSocket } from "../../contexts/SocketIO/useSocket.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, technician: selectTechnician }); const mapDispatchToProps = (dispatch) => ({ - setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })) + setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })), + insertAuditTrail: ({ jobid, operation, type }) => + dispatch( + insertAuditTrail({ + jobid, + operation, + type + }) + ) }); export default connect(mapStateToProps, mapDispatchToProps)(ProductionListDetail); -export function ProductionListDetail({ bodyshop, jobs, setPrintCenterContext, technician }) { +export function ProductionListDetail({ bodyshop, jobs, setPrintCenterContext, technician, insertAuditTrail }) { const search = queryString.parse(useLocation().search); const history = useNavigate(); const { selected } = search; @@ -58,6 +68,7 @@ export function ProductionListDetail({ bodyshop, jobs, setPrintCenterContext, te fetchPolicy: "network-only", nextFetchPolicy: "network-only" }); + const [updateJob] = useMutation(UPDATE_JOB); return ( {!technician ? : null} + {!technician && ( + + )} {!technician ? : null} From 517eca0900aea5835983cf4a36f82c4e748106ea Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 25 Mar 2025 14:53:09 -0400 Subject: [PATCH 32/33] Revert "Merge remote-tracking branch 'origin/release/2025-02-28' into feature/IO-3092-imgproxy" This reverts commit ba9b248b1f46fa2a36d6f3cdf4435db77e449f81, reversing changes made to 0810798d3019afba5591d334c8d685d494c4495c. --- .../parts-order-modal.component.jsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/src/components/parts-order-modal/parts-order-modal.component.jsx b/client/src/components/parts-order-modal/parts-order-modal.component.jsx index 4fc3a5789..c3ade9187 100644 --- a/client/src/components/parts-order-modal/parts-order-modal.component.jsx +++ b/client/src/components/parts-order-modal/parts-order-modal.component.jsx @@ -1,16 +1,17 @@ import { DeleteFilled, DownOutlined, WarningFilled } from "@ant-design/icons"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Checkbox, Divider, Dropdown, Form, Input, InputNumber, Radio, Select, Space, Tag } from "antd"; +import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -32,7 +33,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState, }); const { t } = useTranslation(); - const handleClick = ({ item }) => { + const handleClick = ({ item, key, keyPath }) => { form.setFieldsValue({ comments: item.props.value }); }; @@ -97,18 +98,17 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState, )} - {!isReturn && ( - - - {t("parts_orders.labels.parts_order")} - {t("parts_orders.labels.sublet_order")} - - - )} + + + + {t("parts_orders.labels.parts_order")} + {t("parts_orders.labels.sublet_order")} + + {t("parts_orders.labels.inthisorder")} - {(fields, { remove, move }) => { + {(fields, { add, remove, move }) => { return (
{fields.map((field, index) => ( From 02f5f1985ca089e7752c9d63a9c3d2fbc05ced3b Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 25 Mar 2025 15:01:50 -0400 Subject: [PATCH 33/33] release/2025-03-28 - Up two deps --- package-lock.json | 138 ++++++++-------------------------------------- package.json | 4 +- 2 files changed, 26 insertions(+), 116 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66794a554..806ee2a13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,8 @@ "@aws-sdk/client-secrets-manager": "^3.772.0", "@aws-sdk/client-ses": "^3.772.0", "@aws-sdk/credential-provider-node": "^3.772.0", - "@aws-sdk/lib-storage": "^3.743.0", - "@aws-sdk/s3-request-presigner": "^3.731.1", + "@aws-sdk/lib-storage": "^3.774.0", + "@aws-sdk/s3-request-presigner": "^3.774.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -798,14 +798,14 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.743.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.743.0.tgz", - "integrity": "sha512-Rf/5sljlEJRVtB5C4UjLCOIcK2ODZet9rQsRtsn0bIc2byURbpOdqIGvfEcKWPayoXCS4dC/5bdjhL1zhZ0TMg==", + "version": "3.774.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.774.0.tgz", + "integrity": "sha512-xB3rD+F5pt+JLJaUt5eakCJ3+CUa8PXk9nxgN2VozfpuvuR6A/l3lnxmP5wYLhw1I9hxJLV9AD1/QYcibdBjtQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.2", - "@smithy/smithy-client": "^4.1.2", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/smithy-client": "^4.1.6", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", @@ -815,7 +815,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.743.0" + "@aws-sdk/client-s3": "^3.774.0" } }, "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { @@ -1067,95 +1067,18 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.731.1.tgz", - "integrity": "sha512-GdG0pXkcTgBpenouB834FoCHyLaivV2rGQn7OEQBiT8SBaTxSackZ6tGlJQAlzZQkiQfE/NePUJU7DczJZZvrg==", + "version": "3.774.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.774.0.tgz", + "integrity": "sha512-vD37Nq7+ChUkXSoDqkNMXu37R8kRDUo13pOfYgesSI4HA970fjXP1T4Mf2131Ms/NuYbBHNn330+3MAkbbYKxg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.731.0", - "@aws-sdk/types": "3.731.0", - "@aws-sdk/util-format-url": "3.731.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/core": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.731.0.tgz", - "integrity": "sha512-ithBN1VWASkvAIlozJmenqDvNnFddr/SZXAs58+jCnBHgy3tXLHABZGVNCjetZkHRqNdXEO1kirnoxaFeXMeDA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.731.0", - "@smithy/core": "^3.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/signature-v4": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-middleware": "^4.0.0", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.731.0.tgz", - "integrity": "sha512-J9aKyQaVoec5eWTSDfO4h2sKHNP0wTzN15LFcHnkD+e/d0rdmOi7BTkkbJrIaynma9WShIasmrtM3HNi9GiiTA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.731.0", - "@aws-sdk/types": "3.731.0", - "@aws-sdk/util-arn-parser": "3.723.0", - "@smithy/core": "^3.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/signature-v4": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-stream": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.731.0.tgz", - "integrity": "sha512-1r/b4Os15dR+BCVRRLVQJMF7Krq6xX6IKHxN43kuvODYWz8Nv3XDlaSpeRpAzyJuzW/fTp4JgE+z0+gmJfdEeA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.731.0", - "@aws-sdk/types": "3.731.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/signature-v4": "^5.0.0", - "@smithy/types": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/s3-request-presigner/node_modules/@aws-sdk/types": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", - "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.0.0", + "@aws-sdk/signature-v4-multi-region": "3.774.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-format-url": "3.734.0", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -1237,27 +1160,14 @@ } }, "node_modules/@aws-sdk/util-format-url": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.731.0.tgz", - "integrity": "sha512-wZHObjnYmiz8wFlUQ4/5dHsT7k0at+GvZM02LgvshcRJLnFjYdrzjelMKuNynd/NNK3gLgTsFTGuIgPpz9r4rA==", + "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==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.731.0", - "@smithy/querystring-builder": "^4.0.0", - "@smithy/types": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-format-url/node_modules/@aws-sdk/types": { - "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", - "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.734.0", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { diff --git a/package.json b/package.json index 05a8eb327..9f2bf57f2 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "@aws-sdk/client-secrets-manager": "^3.772.0", "@aws-sdk/client-ses": "^3.772.0", "@aws-sdk/credential-provider-node": "^3.772.0", - "@aws-sdk/lib-storage": "^3.743.0", - "@aws-sdk/s3-request-presigner": "^3.731.1", + "@aws-sdk/lib-storage": "^3.774.0", + "@aws-sdk/s3-request-presigner": "^3.774.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0",