From 12c87ed68953297d7a4c8ce124079b9b98d902b3 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 23 Apr 2025 18:48:35 -0700 Subject: [PATCH 01/22] IO-3217 OTSL Labor Type Signed-off-by: Allan Carr --- .../jobs-available-table.container.jsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx index 0e41c3997..c27423821 100644 --- a/client/src/components/jobs-available-table/jobs-available-table.container.jsx +++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx @@ -4,11 +4,12 @@ import { Col, Row } from "antd"; import Axios from "axios"; import _ from "lodash"; import queryString from "query-string"; -import React, { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; import { createStructuredSelector } from "reselect"; +import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { DELETE_AVAILABLE_JOB, @@ -33,7 +34,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util"; import HeaderFields from "./jobs-available-supplement.headerfields"; import JobsAvailableTableComponent from "./jobs-available-table.component"; -import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -195,7 +195,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail await deleteJob({ variables: { id: estData.id } - }).then((r) => { + }).then(() => { refetch(); setInsertLoading(false); }); @@ -315,7 +315,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail deleteJob({ variables: { id: estData.id } - }).then((r) => { + }).then(() => { refetch(); setInsertLoading(false); }); @@ -372,7 +372,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail loadEstData({ variables: { id: record.id } }); modalSearchState[1](record.clm_no); setJobModalVisible(true); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line }, []); useEffect(() => { @@ -456,7 +456,7 @@ function replaceEmpty(someObj, replaceValue = null) { return JSON.parse(temp); } -async function CheckTaxRatesUSA(estData, bodyshop) { +async function CheckTaxRatesUSA(estData) { if (!estData.parts_tax_rates?.PAM) { estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC; } @@ -568,7 +568,7 @@ async function CheckTaxRates(estData, bodyshop) { }); //} } -function ResolveCCCLineIssues(estData, bodyshop) { +function ResolveCCCLineIssues(estData) { //Find all misc amounts, populate them to the act price. //This needs to be done before cleansing unq_seq since some misc prices could move over. estData.joblines.data.forEach((line) => { @@ -585,6 +585,9 @@ function ResolveCCCLineIssues(estData, bodyshop) { // line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`; line.mod_lbr_ty = "LAR"; } + if (line.mod_lbr_ty === "OTSL") { + line.mod_lbr_ty = line.mod_lbr_hrs === 0 ? null : "LAB"; + } } }); }); From 9ab41308e722e1d29b84a608a87700742c9b7cdf Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 15 May 2025 12:53:41 -0700 Subject: [PATCH 02/22] IO-3066 add EMS upload functionality. --- server/data/data.js | 3 ++- server/data/emsUpload.js | 22 ++++++++++++++++++++++ server/routes/miscellaneousRoutes.js | 3 +++ server/utils/s3.js | 16 ++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 server/data/emsUpload.js diff --git a/server/data/data.js b/server/data/data.js index efd662c13..e89d041b9 100644 --- a/server/data/data.js +++ b/server/data/data.js @@ -4,4 +4,5 @@ exports.chatter = require("./chatter").default; exports.claimscorp = require("./claimscorp").default; exports.kaizen = require("./kaizen").default; exports.usageReport = require("./usageReport").default; -exports.podium = require("./podium").default; \ No newline at end of file +exports.podium = require("./podium").default; +exports.emsUpload = require("./emsUpload").default; \ No newline at end of file diff --git a/server/data/emsUpload.js b/server/data/emsUpload.js new file mode 100644 index 000000000..417454ae4 --- /dev/null +++ b/server/data/emsUpload.js @@ -0,0 +1,22 @@ +const moment = require("moment-timezone"); +const logger = require("../utils/logger"); +const s3Client = require("../utils/s3"); // Using the S3 client utilities with LocalStack support + +const emsUpload = async (req, res) => { + try { + const { bodyshopid, ciecaid, clm_no, ownr_ln } = req.body; + const presignedUrl = await s3Client.getPresignedUrl({ + bucketName: process.env.S3_EMS_UPLOAD_BUCKET, + key: `${bodyshopid}/${ciecaid}-${clm_no}-${ownr_ln}-${moment().format("YYYY-MM-DD--HH-mm-ss")}.zip` + }); + res.status(200).json({ presignedUrl }); + } catch (error) { + logger.log("usage-report-email-error", "ERROR", req?.user?.email, null, { + error: error.message, + stack: error.stack + }); + res.status(500).json({ error: error.message, stack: error.stack }); + } +}; + +exports.default = emsUpload; \ No newline at end of file diff --git a/server/routes/miscellaneousRoutes.js b/server/routes/miscellaneousRoutes.js index 40666302f..b0360d488 100644 --- a/server/routes/miscellaneousRoutes.js +++ b/server/routes/miscellaneousRoutes.js @@ -138,6 +138,9 @@ router.post("/canvastest", validateFirebaseIdTokenMiddleware, canvastest); // Alert Check router.post("/alertcheck", eventAuthorizationMiddleware, alertCheck); +//EMS Upload +router.post("/emsupload", validateFirebaseIdTokenMiddleware, data.emsUpload); + // Redis Cache Routes router.post("/bodyshop-cache", eventAuthorizationMiddleware, updateBodyshopCache); diff --git a/server/utils/s3.js b/server/utils/s3.js index 8b9251e03..2ba1f0d47 100644 --- a/server/utils/s3.js +++ b/server/utils/s3.js @@ -9,6 +9,7 @@ const { const { defaultProvider } = require("@aws-sdk/credential-provider-node"); const { InstanceRegion } = require("./instanceMgr"); const { isString, isEmpty } = require("lodash"); +const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); const createS3Client = () => { const S3Options = { @@ -95,6 +96,17 @@ const createS3Client = () => { throw error; } }; + + const getPresignedUrl = async ({ bucketName, key }) => { + const command = new PutObjectCommand({ + Bucket: bucketName, + Key: key, + StorageClass: "INTELLIGENT_TIERING" + }); + const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 360 }); + return presignedUrl; + } + return { uploadFileToS3, downloadFileFromS3, @@ -102,8 +114,12 @@ const createS3Client = () => { deleteFileFromS3, copyFileInS3, fileExistsInS3, + getPresignedUrl, ...s3Client }; }; + + + module.exports = createS3Client(); From 8b89e2eb9df891f2b9637e86ff86a84721e9f86e Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Thu, 15 May 2025 15:27:59 -0700 Subject: [PATCH 03/22] IO-3210 Podium Datapump Signed-off-by: Allan Carr --- server/data/podium.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/data/podium.js b/server/data/podium.js index 69dfd3226..9f6327ef4 100644 --- a/server/data/podium.js +++ b/server/data/podium.js @@ -185,7 +185,7 @@ async function uploadViaSFTP(csvObj) { await sftp.connect(ftpSetup); try { - csvObj.result = await sftp.put(Buffer.from(csvObj.xml), `${csvObj.filename}`); + csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`); logger.log("podium-sftp-upload", "DEBUG", "api", csvObj.bodyshopid, { imexshopid: csvObj.imexshopid, filename: csvObj.filename, From 61276bb2d116194c34e6c49b48cbe2d35f1877a1 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 15 May 2025 15:35:56 -0700 Subject: [PATCH 04/22] IO-2328 change querystring versions. --- package-lock.json | 74 ++++++++++++++--------------------------------- package.json | 2 +- 2 files changed, 22 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06038085f..902aa54a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,7 @@ "node-persist": "^4.0.4", "nodemailer": "^6.10.0", "phone": "^3.1.58", - "query-string": "^9.1.2", + "query-string": "7.1.3", "recursive-diff": "^1.0.9", "rimraf": "^6.0.1", "skia-canvas": "^2.0.2", @@ -5853,12 +5853,12 @@ } }, "node_modules/decode-uri-component": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz", - "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=0.10" } }, "node_modules/decompress-response": { @@ -7102,15 +7102,12 @@ } }, "node_modules/filter-obj": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz", - "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/finalhandler": { @@ -8132,24 +8129,6 @@ "node": ">=10" } }, - "node_modules/intuit-oauth/node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/intuit-oauth/node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/intuit-oauth/node_modules/query-string": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", @@ -8168,15 +8147,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/intuit-oauth/node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ioredis": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", @@ -10348,17 +10318,18 @@ } }, "node_modules/query-string": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.1.2.tgz", - "integrity": "sha512-s3UlTyjxRux4KjwWaJsjh1Mp8zoCkSGKirbD9H89pEM9UOZsfpRZpdfzvsy2/mGlLfC3NnYVpy2gk7jXITHEtA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", "license": "MIT", "dependencies": { - "decode-uri-component": "^0.4.1", - "filter-obj": "^5.1.0", - "split-on-first": "^3.0.0" + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11496,15 +11467,12 @@ } }, "node_modules/split-on-first": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz", - "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/sprintf-js": { diff --git a/package.json b/package.json index ce5dcaa0c..742d8ab28 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "node-persist": "^4.0.4", "nodemailer": "^6.10.0", "phone": "^3.1.58", - "query-string": "^9.1.2", + "query-string": "7.1.3", "recursive-diff": "^1.0.9", "rimraf": "^6.0.1", "skia-canvas": "^2.0.2", From 392988ae1133c271bfa519931bdd84d39cd479c5 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 15 May 2025 15:46:45 -0700 Subject: [PATCH 05/22] Io-3066 resolve typo. --- server/data/emsUpload.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/data/emsUpload.js b/server/data/emsUpload.js index 417454ae4..a59f0d385 100644 --- a/server/data/emsUpload.js +++ b/server/data/emsUpload.js @@ -11,7 +11,7 @@ const emsUpload = async (req, res) => { }); res.status(200).json({ presignedUrl }); } catch (error) { - logger.log("usage-report-email-error", "ERROR", req?.user?.email, null, { + logger.log("ems-upload-presign-error", "ERROR", req?.user?.email, null, { error: error.message, stack: error.stack }); From 96cba0aaab387c4add2d46b74da616a69ac5c276 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 15 May 2025 18:54:55 -0400 Subject: [PATCH 06/22] Clear Stage --- client/package-lock.json | 162 ++++++++++++------ client/package.json | 10 +- .../down.sql | 4 + .../up.sql | 2 + .../down.sql | 1 + .../up.sql | 2 + 6 files changed, 121 insertions(+), 60 deletions(-) create mode 100644 hasura/migrations/1747158530097_alter_table_public_bodyshops_add_column_enforce_sms_consent/down.sql create mode 100644 hasura/migrations/1747158530097_alter_table_public_bodyshops_add_column_enforce_sms_consent/up.sql create mode 100644 hasura/migrations/1747158981002_create_table_public_phone_number_consent/down.sql create mode 100644 hasura/migrations/1747158981002_create_table_public_phone_number_consent/up.sql diff --git a/client/package-lock.json b/client/package-lock.json index d0cd3d682..496885fb9 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -15,13 +15,13 @@ "@fingerprintjs/fingerprintjs": "^4.6.1", "@firebase/analytics": "^0.10.13", "@firebase/app": "^0.12.1", - "@firebase/auth": "^1.10.2", - "@firebase/firestore": "^4.7.12", + "@firebase/auth": "^1.10.4", + "@firebase/firestore": "^4.7.14", "@firebase/messaging": "^0.12.18", "@jsreport/browser-client": "^3.1.0", - "@reduxjs/toolkit": "^2.8.1", + "@reduxjs/toolkit": "^2.8.2", "@sentry/cli": "^2.45.0", - "@sentry/react": "^9.18.0", + "@sentry/react": "^9.19.0", "@sentry/vite-plugin": "^3.4.0", "@splitsoftware/splitio-react": "^2.1.1", "@tanem/react-nprogress": "^5.0.53", @@ -108,7 +108,7 @@ "eslint-plugin-react": "^7.37.5", "globals": "^15.15.0", "jsdom": "^26.0.0", - "memfs": "^4.17.1", + "memfs": "^4.17.2", "os-browserify": "^0.3.0", "playwright": "^1.51.1", "react-error-overlay": "^6.1.0", @@ -2963,14 +2963,14 @@ } }, "node_modules/@firebase/auth": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.2.tgz", - "integrity": "sha512-HHudcj3CJyXpoMKslNOVHGSNJdAUjvy5xBA/G/uPb32QFqvx5F3EW9RDYvve2IHEN7Vpc1QTkk/28J32x83UGA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.4.tgz", + "integrity": "sha512-rZQZQkn5x7BcHenYJi9RYWoOMJHdM/CsF6DMclb/CKbntzjUaZj+R45Iyzf/BFUJ9L2sA4bNPhJK9x+l9VKvLQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.14", + "@firebase/component": "0.6.16", "@firebase/logger": "0.4.4", - "@firebase/util": "1.11.1", + "@firebase/util": "1.11.3", "tslib": "^2.1.0" }, "engines": { @@ -2986,6 +2986,32 @@ } } }, + "node_modules/@firebase/auth/node_modules/@firebase/component": { + "version": "0.6.16", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.16.tgz", + "integrity": "sha512-whx+e3pgC3J9O6t4LOB8jiLk3tpWtnXaQ+xt/ys/4IGUPRI+nnWooVdtWrEnMga/gT03ug9SdEAEJLl6I1BIlg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.11.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/auth/node_modules/@firebase/util": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.3.tgz", + "integrity": "sha512-4wYnOV9FpwdCq3rHQOCrdx4AQBUbfH1p2DhWGQxlQ+D3Xl/wSxc/HttcyPN4NNFiynxoNCFGWQH/zdhRfxP1Zg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@firebase/component": { "version": "0.6.14", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.14.tgz", @@ -3000,14 +3026,14 @@ } }, "node_modules/@firebase/firestore": { - "version": "4.7.12", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.12.tgz", - "integrity": "sha512-50KRdSp8xA7+G0wfWxlnCoEN951mt8BVdLMxeP57Rehj2DqIb41q6Fc6JH0dfQ4TlMqWua1YfVY1jPEAaHVF9w==", + "version": "4.7.14", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.14.tgz", + "integrity": "sha512-YLz71p96ACfILNjnqh7H6ilsT3AZZyDpCCE+wpl8mJklAbdpyd2ahNIqS1eBCjseqls8vQO/XTaIcbpkSgQFIg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.14", + "@firebase/component": "0.6.16", "@firebase/logger": "0.4.4", - "@firebase/util": "1.11.1", + "@firebase/util": "1.11.3", "@firebase/webchannel-wrapper": "1.0.3", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", @@ -3020,6 +3046,32 @@ "@firebase/app": "0.x" } }, + "node_modules/@firebase/firestore/node_modules/@firebase/component": { + "version": "0.6.16", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.16.tgz", + "integrity": "sha512-whx+e3pgC3J9O6t4LOB8jiLk3tpWtnXaQ+xt/ys/4IGUPRI+nnWooVdtWrEnMga/gT03ug9SdEAEJLl6I1BIlg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.11.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/firestore/node_modules/@firebase/util": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.3.tgz", + "integrity": "sha512-4wYnOV9FpwdCq3rHQOCrdx4AQBUbfH1p2DhWGQxlQ+D3Xl/wSxc/HttcyPN4NNFiynxoNCFGWQH/zdhRfxP1Zg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@firebase/installations": { "version": "0.6.14", "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.14.tgz", @@ -3836,9 +3888,9 @@ "license": "MIT" }, "node_modules/@reduxjs/toolkit": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.1.tgz", - "integrity": "sha512-GLjHS13LiBdiuxSJvfWs3+Cx5yt97mCbuVlDteTusS6VRksPhoWviO8L1e3Re1G94m6lkw/l4pjEEyyNaGf19g==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", + "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -4458,50 +4510,50 @@ "license": "MIT" }, "node_modules/@sentry-internal/browser-utils": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.18.0.tgz", - "integrity": "sha512-TwSlmgYpHhe55JpOcVApkM0XcXZh1/cYuEPKPFgeaaPD8BrQrLJJvwKxnonSWXOhdnkJxi4GgK7j7mw57PS4aA==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.19.0.tgz", + "integrity": "sha512-DlEHX4eIHe5yIuh/cFu9OiaFuk1CTnFK95zj61I7Q2fxmN43dIwC3xAAGJ/Hy+GDQi7kU+BiS2sudSHSTq81BA==", "license": "MIT", "dependencies": { - "@sentry/core": "9.18.0" + "@sentry/core": "9.19.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/feedback": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.18.0.tgz", - "integrity": "sha512-QlrB8oQK+5bfhbgK6yHF6rLwLNJ9XuGblTc51yVkm4d4jn4W/HDyaNqMfQF+JXdTiFatl8oz2xdKR8kGK8kXyg==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.19.0.tgz", + "integrity": "sha512-yixRrv4NfpjhFW56AuUTjVwZlignB9FWAXXyrmRP3SsFeJCFrAsSD8HOxV9RXNr9ePYl7MEU0Agi43YWhJsiAw==", "license": "MIT", "dependencies": { - "@sentry/core": "9.18.0" + "@sentry/core": "9.19.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.18.0.tgz", - "integrity": "sha512-2A32FFwrlZtdpBruvpcLEfucu6BpyqOk3F4Bo5smM/5q7u0pa7q5d9FSY5l3nwKEAFAoLGv3hcCb+8wxMm50xA==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.19.0.tgz", + "integrity": "sha512-i/X9brRchbAF25yjxLTI7E8eoESRPBgIyQOWoWRXXt2n51iBRTjLXSaEfGvjdN+qrMq/yd6nC1/UqJVxXHeIhA==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.18.0", - "@sentry/core": "9.18.0" + "@sentry-internal/browser-utils": "9.19.0", + "@sentry/core": "9.19.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.18.0.tgz", - "integrity": "sha512-3DEyQLmHcYgcwJ8n8eMhI6bhhawPuMc2xTT+Az8gXMqCO/X9ZACpipAmhXFjYP9Ptl+w0Vh3nllJw+gXc/DOsg==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.19.0.tgz", + "integrity": "sha512-YC8yrOjuKSfQgGniJnzkdbFsWEPTlNpzeeYPTxS4ouH1FwfGrSkPmcddjor2YHaLfiuHHqQ/Vvq70n+zruJH7A==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "9.18.0", - "@sentry/core": "9.18.0" + "@sentry-internal/replay": "9.19.0", + "@sentry/core": "9.19.0" }, "engines": { "node": ">=18" @@ -4517,16 +4569,16 @@ } }, "node_modules/@sentry/browser": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.18.0.tgz", - "integrity": "sha512-0SWfp4J2+mH4lZOcHfyIwt9VoGD7yCGQE1cm0BPcLwKnrVQeXHtUXNYNy8HTHSjTGyoFDhEAYelE/tdA3OLcWQ==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.19.0.tgz", + "integrity": "sha512-efKfPQ0yQkdIkC7qJ5TIHxnecLNENGUYl1YD/TC8yyzW2JRf/3OYo5yg1hY2rhsP5RwQShXlT7uA03ABVIkA4A==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.18.0", - "@sentry-internal/feedback": "9.18.0", - "@sentry-internal/replay": "9.18.0", - "@sentry-internal/replay-canvas": "9.18.0", - "@sentry/core": "9.18.0" + "@sentry-internal/browser-utils": "9.19.0", + "@sentry-internal/feedback": "9.19.0", + "@sentry-internal/replay": "9.19.0", + "@sentry-internal/replay-canvas": "9.19.0", + "@sentry/core": "9.19.0" }, "engines": { "node": ">=18" @@ -4899,22 +4951,22 @@ } }, "node_modules/@sentry/core": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.18.0.tgz", - "integrity": "sha512-kRVH8BqMiaU2FTHYa68zNlAloS43jl4XtIEHkLKVH/7gUtwRmM4Gqj8P7RTrZdO1Lo7ksYnGj+AG05Z09CRbOw==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.19.0.tgz", + "integrity": "sha512-I41rKpMJHHZb0z0Nja+Lxto6IkEEmX3uWjnECypF8Z1HIjeJB0+PXl8p/7TeaKYqw2J2GYcRTg7jQZDmvKle1w==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@sentry/react": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.18.0.tgz", - "integrity": "sha512-1cCLYZrZ2gu6H8eE83DC47kLf+pzD1Rim3dDoOEvwt1F5cD3K/DBeIhoHZaXqBeQxuVyHXOOLXSAC/CIuas5Aw==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.19.0.tgz", + "integrity": "sha512-tHuzPVbqKsONlFQsy7FqqGjBaujQoLRIDBLlPPMNoiGvP3rodBl6t1v5zoNAq4m47i3MhvpLEYf6C00j1w5UMQ==", "license": "MIT", "dependencies": { - "@sentry/browser": "9.18.0", - "@sentry/core": "9.18.0", + "@sentry/browser": "9.19.0", + "@sentry/core": "9.19.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -11833,9 +11885,9 @@ } }, "node_modules/memfs": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.1.tgz", - "integrity": "sha512-thuTRd7F4m4dReCIy7vv4eNYnU6XI/tHMLSMMHLiortw/Y0QxqKtinG523U2aerzwYWGi606oBP4oMPy4+edag==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", + "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/client/package.json b/client/package.json index 1779b36c5..4018d9629 100644 --- a/client/package.json +++ b/client/package.json @@ -14,13 +14,13 @@ "@fingerprintjs/fingerprintjs": "^4.6.1", "@firebase/analytics": "^0.10.13", "@firebase/app": "^0.12.1", - "@firebase/auth": "^1.10.2", - "@firebase/firestore": "^4.7.12", + "@firebase/auth": "^1.10.4", + "@firebase/firestore": "^4.7.14", "@firebase/messaging": "^0.12.18", "@jsreport/browser-client": "^3.1.0", - "@reduxjs/toolkit": "^2.8.1", + "@reduxjs/toolkit": "^2.8.2", "@sentry/cli": "^2.45.0", - "@sentry/react": "^9.18.0", + "@sentry/react": "^9.19.0", "@sentry/vite-plugin": "^3.4.0", "@splitsoftware/splitio-react": "^2.1.1", "@tanem/react-nprogress": "^5.0.53", @@ -148,7 +148,7 @@ "eslint-plugin-react": "^7.37.5", "globals": "^15.15.0", "jsdom": "^26.0.0", - "memfs": "^4.17.1", + "memfs": "^4.17.2", "os-browserify": "^0.3.0", "playwright": "^1.51.1", "react-error-overlay": "^6.1.0", diff --git a/hasura/migrations/1747158530097_alter_table_public_bodyshops_add_column_enforce_sms_consent/down.sql b/hasura/migrations/1747158530097_alter_table_public_bodyshops_add_column_enforce_sms_consent/down.sql new file mode 100644 index 000000000..fd9787812 --- /dev/null +++ b/hasura/migrations/1747158530097_alter_table_public_bodyshops_add_column_enforce_sms_consent/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"."bodyshops" add column "enforce_sms_consent" boolean +-- null; diff --git a/hasura/migrations/1747158530097_alter_table_public_bodyshops_add_column_enforce_sms_consent/up.sql b/hasura/migrations/1747158530097_alter_table_public_bodyshops_add_column_enforce_sms_consent/up.sql new file mode 100644 index 000000000..dfb9ef133 --- /dev/null +++ b/hasura/migrations/1747158530097_alter_table_public_bodyshops_add_column_enforce_sms_consent/up.sql @@ -0,0 +1,2 @@ +alter table "public"."bodyshops" add column "enforce_sms_consent" boolean + null; diff --git a/hasura/migrations/1747158981002_create_table_public_phone_number_consent/down.sql b/hasura/migrations/1747158981002_create_table_public_phone_number_consent/down.sql new file mode 100644 index 000000000..d113cb9a7 --- /dev/null +++ b/hasura/migrations/1747158981002_create_table_public_phone_number_consent/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."phone_number_consent"; diff --git a/hasura/migrations/1747158981002_create_table_public_phone_number_consent/up.sql b/hasura/migrations/1747158981002_create_table_public_phone_number_consent/up.sql new file mode 100644 index 000000000..fc7cddeba --- /dev/null +++ b/hasura/migrations/1747158981002_create_table_public_phone_number_consent/up.sql @@ -0,0 +1,2 @@ +CREATE TABLE "public"."phone_number_consent" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "bodyshopid" uuid NOT NULL, "phone_number" text NOT NULL, "consent_status" boolean NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "consent_updated_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id") , FOREIGN KEY ("bodyshopid") REFERENCES "public"."bodyshops"("id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("bodyshopid", "phone_number")); +CREATE EXTENSION IF NOT EXISTS pgcrypto; From 9a3a971da6a749f22ca6b2cb2d2f739367294e52 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 15 May 2025 18:55:20 -0400 Subject: [PATCH 07/22] Clear Stage --- hasura/metadata/tables.yaml | 64 +++++ package-lock.json | 476 ++++++++++++++++++------------------ package.json | 20 +- 3 files changed, 312 insertions(+), 248 deletions(-) diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index cd68acf61..1b4faac05 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -957,6 +957,7 @@ - enforce_conversion_category - enforce_conversion_csr - enforce_referral + - enforce_sms_consent - entegral_configuration - entegral_id - features @@ -1067,6 +1068,7 @@ - enforce_conversion_category - enforce_conversion_csr - enforce_referral + - enforce_sms_consent - federal_tax_id - id - inhousevendorid @@ -5861,6 +5863,68 @@ template_engine: Kriti url: '{{$base_url}}/opensearch' version: 2 +- table: + name: phone_number_consent + schema: public + object_relationships: + - name: bodyshop + using: + foreign_key_constraint_on: bodyshopid + insert_permissions: + - role: user + permission: + check: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + columns: + - consent_status + - phone_number + - consent_updated_at + - created_at + - updated_at + - bodyshopid + - id + comment: "" + select_permissions: + - role: user + permission: + columns: + - consent_status + - phone_number + - consent_updated_at + - created_at + - updated_at + - bodyshopid + - id + filter: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + comment: "" + update_permissions: + - role: user + permission: + columns: + - bodyshopid + - consent_status + - consent_updated_at + - created_at + - phone_number + - updated_at + filter: {} + check: null + comment: "" - table: name: phonebook schema: public diff --git a/package-lock.json b/package-lock.json index 06038085f..9f5a49234 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,14 @@ "version": "0.2.0", "license": "UNLICENSED", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.808.0", - "@aws-sdk/client-elasticache": "^3.808.0", - "@aws-sdk/client-s3": "^3.808.0", - "@aws-sdk/client-secrets-manager": "^3.808.0", - "@aws-sdk/client-ses": "^3.808.0", - "@aws-sdk/credential-provider-node": "^3.808.0", - "@aws-sdk/lib-storage": "^3.808.0", - "@aws-sdk/s3-request-presigner": "^3.808.0", + "@aws-sdk/client-cloudwatch-logs": "^3.810.0", + "@aws-sdk/client-elasticache": "^3.810.0", + "@aws-sdk/client-s3": "^3.810.0", + "@aws-sdk/client-secrets-manager": "^3.810.0", + "@aws-sdk/client-ses": "^3.810.0", + "@aws-sdk/credential-provider-node": "^3.810.0", + "@aws-sdk/lib-storage": "^3.810.0", + "@aws-sdk/s3-request-presigner": "^3.810.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -29,9 +29,9 @@ "cloudinary": "^2.6.1", "compression": "^1.8.0", "cookie-parser": "^1.4.7", - "cors": "2.8.5", + "cors": "^2.8.5", "crisp-status-reporter": "^1.2.2", - "dd-trace": "^5.51.0", + "dd-trace": "^5.52.0", "dinero.js": "^1.9.1", "dotenv": "^16.4.5", "express": "^4.21.1", @@ -284,26 +284,26 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.808.0.tgz", - "integrity": "sha512-bD57b5re12DS+GECrBR2vmwcREBDgoEj33gAV1zhTdQ38ZgyA2yhaFxfaltB+qYRfPqu95pcc+ZQTnI0Nyu1ag==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.810.0.tgz", + "integrity": "sha512-1FLkuei0pnBIGJJktYhY03LNcdGiYyxn8TcugUl4A3+7fOs9HCmcA+a5vTUThvnDqFKn4xtptQgYo4oiSLxtwA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.808.0", - "@aws-sdk/credential-provider-node": "3.808.0", + "@aws-sdk/core": "3.810.0", + "@aws-sdk/credential-provider-node": "3.810.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.808.0", + "@aws-sdk/middleware-user-agent": "3.810.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.808.0", + "@aws-sdk/util-user-agent-node": "3.810.0", "@smithy/config-resolver": "^4.1.2", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/eventstream-serde-browser": "^4.0.2", "@smithy/eventstream-serde-config-resolver": "^4.1.0", "@smithy/eventstream-serde-node": "^4.0.2", @@ -311,21 +311,21 @@ "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.4", - "@smithy/middleware-retry": "^4.1.5", - "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.12", - "@smithy/util-defaults-mode-node": "^4.0.12", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", @@ -352,45 +352,45 @@ } }, "node_modules/@aws-sdk/client-elasticache": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.808.0.tgz", - "integrity": "sha512-sUfbx2H8wEYLLHZLCdPG0h5CtcqQ7y2pB4BLnzjIQMrlIuxzqCumpww7XVL9yCIPAooPoeKVjUCr9tJkSa6MvQ==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.810.0.tgz", + "integrity": "sha512-tB7bBIoAz+qoFq8b1oR6TdjIru3Ui9VL+TWzM6jxxgS2LtyH3MS6hu0qsJWIVeDZO9MEOdb66u+aQoyxJecasA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.808.0", - "@aws-sdk/credential-provider-node": "3.808.0", + "@aws-sdk/core": "3.810.0", + "@aws-sdk/credential-provider-node": "3.810.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.808.0", + "@aws-sdk/middleware-user-agent": "3.810.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.808.0", + "@aws-sdk/util-user-agent-node": "3.810.0", "@smithy/config-resolver": "^4.1.2", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.4", - "@smithy/middleware-retry": "^4.1.5", - "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.12", - "@smithy/util-defaults-mode-node": "^4.0.12", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", @@ -403,35 +403,35 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.808.0.tgz", - "integrity": "sha512-8RY3Jsm84twmYfiqnMkxznuY6pBX7y2GiuEJVdW1ZJLXRDOiCPkTBHsO6jUwppfMua7HRhO2OTAdWr7aSBAdPw==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.810.0.tgz", + "integrity": "sha512-wM8M0BrqRkbZ9fGaLmAl24CUgVmmLjiKuNTqGOHsdCIc7RV+IGv5CnGK7ciOltAttrFBxvDlhy2lg5F8gNw8Bg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.808.0", - "@aws-sdk/credential-provider-node": "3.808.0", + "@aws-sdk/core": "3.810.0", + "@aws-sdk/credential-provider-node": "3.810.0", "@aws-sdk/middleware-bucket-endpoint": "3.808.0", "@aws-sdk/middleware-expect-continue": "3.804.0", - "@aws-sdk/middleware-flexible-checksums": "3.808.0", + "@aws-sdk/middleware-flexible-checksums": "3.810.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-location-constraint": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-sdk-s3": "3.808.0", + "@aws-sdk/middleware-sdk-s3": "3.810.0", "@aws-sdk/middleware-ssec": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.808.0", + "@aws-sdk/middleware-user-agent": "3.810.0", "@aws-sdk/region-config-resolver": "3.808.0", - "@aws-sdk/signature-v4-multi-region": "3.808.0", + "@aws-sdk/signature-v4-multi-region": "3.810.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.808.0", + "@aws-sdk/util-user-agent-node": "3.810.0", "@aws-sdk/xml-builder": "3.804.0", "@smithy/config-resolver": "^4.1.2", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/eventstream-serde-browser": "^4.0.2", "@smithy/eventstream-serde-config-resolver": "^4.1.0", "@smithy/eventstream-serde-node": "^4.0.2", @@ -442,21 +442,21 @@ "@smithy/invalid-dependency": "^4.0.2", "@smithy/md5-js": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.4", - "@smithy/middleware-retry": "^4.1.5", - "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.12", - "@smithy/util-defaults-mode-node": "^4.0.12", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", @@ -470,45 +470,45 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.808.0.tgz", - "integrity": "sha512-uEAnM0bXA1KtsI17Fg/8TG4ereiLY0lPqFlYM58MGDNj3mJlBTCokN4VgLBDvxOyx1rEuWH/1LrgsL9d78Kgsw==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.810.0.tgz", + "integrity": "sha512-FKqvgtBOQ69SXFfuMpgx81GmaJMAmCyfb6JjSdSay0Gj585dzb6Jn7ye8zkuy4k6XAt8flnbeaFNORbY9BT38g==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.808.0", - "@aws-sdk/credential-provider-node": "3.808.0", + "@aws-sdk/core": "3.810.0", + "@aws-sdk/credential-provider-node": "3.810.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.808.0", + "@aws-sdk/middleware-user-agent": "3.810.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.808.0", + "@aws-sdk/util-user-agent-node": "3.810.0", "@smithy/config-resolver": "^4.1.2", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.4", - "@smithy/middleware-retry": "^4.1.5", - "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.12", - "@smithy/util-defaults-mode-node": "^4.0.12", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", @@ -535,45 +535,45 @@ } }, "node_modules/@aws-sdk/client-ses": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.808.0.tgz", - "integrity": "sha512-RwVaoHBCbH3TGCqDXVRHPcAA2U9y2DzTx2RahhcwmthH3U22cdADRmOZBwJzZ3Be4pEultsj3jT1wkJ1fuSw4g==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.810.0.tgz", + "integrity": "sha512-QUaW4bJIpqNyIPWvRun8avLDuIfbWOAyspgnG5JSkQtIK3wWeeeR8lXrCNFtgzY0X8MPdy0Ns50YbVEsVWnmMA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.808.0", - "@aws-sdk/credential-provider-node": "3.808.0", + "@aws-sdk/core": "3.810.0", + "@aws-sdk/credential-provider-node": "3.810.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.808.0", + "@aws-sdk/middleware-user-agent": "3.810.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.808.0", + "@aws-sdk/util-user-agent-node": "3.810.0", "@smithy/config-resolver": "^4.1.2", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.4", - "@smithy/middleware-retry": "^4.1.5", - "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.12", - "@smithy/util-defaults-mode-node": "^4.0.12", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", @@ -586,44 +586,44 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.808.0.tgz", - "integrity": "sha512-NxGomD0x9q30LPOXf4x7haOm6l2BJdLEzpiC/bPEXUkf2+4XudMQumMA/hDfErY5hCE19mFAouoO465m3Gl3JQ==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.810.0.tgz", + "integrity": "sha512-Txp/3jHqkfA4BTklQEOGiZ1yTUxg+hITislfaWEzJ904vlDt4DvAljTlhfaz7pceCLA2+LhRlYZYSv7t5b0Ltw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.808.0", + "@aws-sdk/core": "3.810.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.808.0", + "@aws-sdk/middleware-user-agent": "3.810.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.808.0", + "@aws-sdk/util-user-agent-node": "3.810.0", "@smithy/config-resolver": "^4.1.2", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.4", - "@smithy/middleware-retry": "^4.1.5", - "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.12", - "@smithy/util-defaults-mode-node": "^4.0.12", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", @@ -635,18 +635,18 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.808.0.tgz", - "integrity": "sha512-+nTmxJVIPtAarGq9Fd/uU2qU/Ngfb9EntT0/kwXdKKMI0wU9fQNWi10xSTVeqOtzWERbQpOJgBAdta+v3W7cng==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.810.0.tgz", + "integrity": "sha512-s2IJk+qa/15YZcv3pbdQNATDR+YdYnHf94MrAeVAWubtRLnzD8JciC+gh4LSPp7JzrWSvVOg2Ut1S+0y89xqCg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.804.0", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", @@ -657,12 +657,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.808.0.tgz", - "integrity": "sha512-snPRQnwG9PV4kYHQimo1tenf7P974RcdxkHUThzWSxPEV7HpjxTFYNWGlKbOKBhL4AcgeCVeiZ/j+zveF2lEPA==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.810.0.tgz", + "integrity": "sha512-iwHqF+KryKONfbdFk3iKhhPk4fHxh5QP5fXXR//jhYwmszaLOwc7CLCE9AxhgiMzAs+kV8nBFQZvdjFpPzVGOA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.808.0", + "@aws-sdk/core": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -673,18 +673,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.808.0.tgz", - "integrity": "sha512-gNXjlx3BIUeX7QpVqxbjBxG6zm45lC39QvUIo92WzEJd2OTPcR8TU0OTTsgq/lpn2FrKcISj5qXvhWykd41+CA==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.810.0.tgz", + "integrity": "sha512-SKzjLd+8ugif7yy9sOAAdnPE1vCBHQe6jKgs2AadMpCmWm34DiHz/KuulHdvURUGMIi7CvmaC8aH77twDPYbtg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.808.0", + "@aws-sdk/core": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" @@ -694,20 +694,20 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.808.0.tgz", - "integrity": "sha512-Y53CW0pCvFQQEvtVFwExCCMbTg+6NOl8b3YOuZVzPmVmDoW7M1JIn9IScesqoGERXL3VoXny6nYTsZj+vfpp7Q==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.810.0.tgz", + "integrity": "sha512-H2QCSnxWJ/mj8HTcyHmCmyQ5bO/+imRi4mlBIpUyKjiYKro52WD3gXlGgPIDo2q3UFIHq37kmYvS00i+qIY9tw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.808.0", - "@aws-sdk/credential-provider-env": "3.808.0", - "@aws-sdk/credential-provider-http": "3.808.0", - "@aws-sdk/credential-provider-process": "3.808.0", - "@aws-sdk/credential-provider-sso": "3.808.0", - "@aws-sdk/credential-provider-web-identity": "3.808.0", - "@aws-sdk/nested-clients": "3.808.0", + "@aws-sdk/core": "3.810.0", + "@aws-sdk/credential-provider-env": "3.810.0", + "@aws-sdk/credential-provider-http": "3.810.0", + "@aws-sdk/credential-provider-process": "3.810.0", + "@aws-sdk/credential-provider-sso": "3.810.0", + "@aws-sdk/credential-provider-web-identity": "3.810.0", + "@aws-sdk/nested-clients": "3.810.0", "@aws-sdk/types": "3.804.0", - "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", @@ -718,19 +718,19 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.808.0.tgz", - "integrity": "sha512-lASHlXJ6U5Cpnt9Gs+mWaaSmWcEibr1AFGhp+5UNvfyd+UU2Oiwgbo7rYXygmaVDGkbfXEiTkgYtoNOBSddnWQ==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.810.0.tgz", + "integrity": "sha512-9E3Chv3x+RBM3N1bwLCyvXxoiPAckCI74wG7ePN4F3b/7ieIkbEl/3Hd67j1fnt62Xa1cjUHRu2tz5pdEv5G1Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.808.0", - "@aws-sdk/credential-provider-http": "3.808.0", - "@aws-sdk/credential-provider-ini": "3.808.0", - "@aws-sdk/credential-provider-process": "3.808.0", - "@aws-sdk/credential-provider-sso": "3.808.0", - "@aws-sdk/credential-provider-web-identity": "3.808.0", + "@aws-sdk/credential-provider-env": "3.810.0", + "@aws-sdk/credential-provider-http": "3.810.0", + "@aws-sdk/credential-provider-ini": "3.810.0", + "@aws-sdk/credential-provider-process": "3.810.0", + "@aws-sdk/credential-provider-sso": "3.810.0", + "@aws-sdk/credential-provider-web-identity": "3.810.0", "@aws-sdk/types": "3.804.0", - "@smithy/credential-provider-imds": "^4.0.2", + "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", @@ -741,12 +741,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.808.0.tgz", - "integrity": "sha512-ZLqp+xsQUatoo8pMozcfLwf/pwfXeIk0w3n0Lo/rWBgT3RcdECmmPCRcnkYBqxHQyE66aS9HiJezZUwMYPqh6w==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.810.0.tgz", + "integrity": "sha512-42kE6MLdsmMGp1id3Gisal4MbMiF7PIc0tAznTeIuE8r7cIF8yeQWw/PBOIvjyI57DxbyKzLUAMEJuigUpApCw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.808.0", + "@aws-sdk/core": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -758,14 +758,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.808.0.tgz", - "integrity": "sha512-gWZByAokHX+aps1+syIW/hbKUBrjE2RpPRd/RGQvrBbVVgwsJzsHKsW0zy1B6mgARPG6IahmSUMjNkBCVsiAgw==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.810.0.tgz", + "integrity": "sha512-8WjX6tz+FCvM93Y33gsr13p/HiiTJmVn5AK1O8PTkvHBclQDzmtAW5FdPqTpAJGswLW2FB0xRqdsSMN2dQEjNw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.808.0", - "@aws-sdk/core": "3.808.0", - "@aws-sdk/token-providers": "3.808.0", + "@aws-sdk/client-sso": "3.810.0", + "@aws-sdk/core": "3.810.0", + "@aws-sdk/token-providers": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -777,13 +777,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.808.0.tgz", - "integrity": "sha512-SsGa1Gfa05aJM/qYOtHmfg0OKKW6Fl6kyMCcai63jWDVDYy0QSHcesnqRayJolISkdsVK6bqoWoFcPxiopcFcg==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.810.0.tgz", + "integrity": "sha512-uKQJY0AcPyrvMmfGLo36semgjqJ4vmLTqOSW9u40qQDspRnG73/P09lAO2ntqKlhwvMBt3XfcNnOpyyhKRcOfA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.808.0", - "@aws-sdk/nested-clients": "3.808.0", + "@aws-sdk/core": "3.810.0", + "@aws-sdk/nested-clients": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -794,14 +794,14 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.808.0.tgz", - "integrity": "sha512-gEdiBuqPjmMA0Z2RtppIdcMQH1KDeRM//+DWcVC9gU9pMJ5S/LEwCmWEVh3/C2n/Sehv71b7x0GKGxHO/yBrwA==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.810.0.tgz", + "integrity": "sha512-Ri3Kgkk9XK7wmgKaQUA4ZNgr4lTASFlt2pd4xo7sbpX7K984fYkWe7ecrgbRptIiIYUjeJiYCRb0FS34diSaZw==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.4", - "@smithy/smithy-client": "^4.2.4", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/smithy-client": "^4.2.6", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", @@ -811,7 +811,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.808.0" + "@aws-sdk/client-s3": "^3.810.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { @@ -848,15 +848,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.808.0.tgz", - "integrity": "sha512-NW1yoTYDH2h8ycqMPNkvW3d1XT2vEeXfXclagL2tv82P7Qt7vPXYcObs/YtETvNZ7hdnmOftJ/IJv7YrFC8vtQ==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.810.0.tgz", + "integrity": "sha512-lF5fse+26hluElOtDZMsi5EH50G13OEqglFgpSc6xWnqNhbDc+CnPQRMwTVlOJBDR1/YVbJ15LOKf4pkat46Eg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.808.0", + "@aws-sdk/core": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/is-array-buffer": "^4.0.0", "@smithy/node-config-provider": "^4.1.1", @@ -930,19 +930,19 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.808.0.tgz", - "integrity": "sha512-qvyJTDf0HIsPpZzBUqhNQm5g8stAn2EOwVsaAolsOHuBsdaBAE/s/NgPzazDlSXwdF0ITvsIouUVDCn4fJGJqQ==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.810.0.tgz", + "integrity": "sha512-CmQHPVopJYDiGQOmqn5AcmhksQ9qNETF0VgU251Q4tsP9s3R9nBR1r2bZwLt5+dCtf9UCa7cBw4jXKHtH38JTg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.808.0", + "@aws-sdk/core": "3.810.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-arn-parser": "3.804.0", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/node-config-provider": "^4.1.1", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.2", @@ -969,15 +969,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.808.0.tgz", - "integrity": "sha512-VckV6l5cf/rL3EtgzSHVTTD4mI0gd8UxDDWbKJsxbQ2bpNPDQG2L1wWGLaolTSzjEJ5f3ijDwQrNDbY9l85Mmg==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.810.0.tgz", + "integrity": "sha512-gLMJcqgIq7k9skX8u0Yyi+jil4elbsmLf3TuDuqNdlqiZ44/AKdDFfU3mU5tRUtMfP42a3gvb2U3elP0BIeybQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.808.0", + "@aws-sdk/core": "3.810.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" @@ -987,44 +987,44 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.808.0.tgz", - "integrity": "sha512-NparPojwoBul7XPCasy4psFMJbw7Ys4bz8lVB93ljEUD4VV7mM7zwK27Uhz20B8mBFGmFEoAprPsVymJcK9Vcw==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.810.0.tgz", + "integrity": "sha512-w+tGXFSQjzvJ3j2sQ4GJRdD+YXLTgwLd9eG/A+7pjrv2yLLV70M4HqRrFqH06JBjqT5rsOxonc/QSjROyxk+IA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.808.0", + "@aws-sdk/core": "3.810.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.808.0", + "@aws-sdk/middleware-user-agent": "3.810.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.808.0", + "@aws-sdk/util-user-agent-node": "3.810.0", "@smithy/config-resolver": "^4.1.2", - "@smithy/core": "^3.3.1", + "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.4", - "@smithy/middleware-retry": "^4.1.5", - "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.1.1", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.12", - "@smithy/util-defaults-mode-node": "^4.0.12", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", "@smithy/util-endpoints": "^3.0.4", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", @@ -1053,17 +1053,17 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.808.0.tgz", - "integrity": "sha512-viKWGrb7ZTN2rDkjX5rSkPeyKEVFDiyJS8HWVLBUJGwOLb65wluX+Rr/9fuCFyhCbBzhzpXFdzczZ0kCHOM6Qw==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.810.0.tgz", + "integrity": "sha512-/6TISQT3HUOoY3Gli1EvQVISNGp8XawGKD4igJqwyF3l35VvajzaAfhwk+Tm5TKTCkFh8EFnqWeT+mId5AaUbA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.808.0", + "@aws-sdk/signature-v4-multi-region": "3.810.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-format-url": "3.804.0", - "@smithy/middleware-endpoint": "^4.1.4", + "@smithy/middleware-endpoint": "^4.1.6", "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.4", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, @@ -1072,12 +1072,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.808.0.tgz", - "integrity": "sha512-lQuEB6JK81eKV7fdiktmRq06Y1KCcJbx9fLf7b19nSfYUbJSn/kfSpHPv/tOkJK2HKnN61JsfG19YU8k4SOU8Q==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.810.0.tgz", + "integrity": "sha512-6F6evHRrA0OG/H67YuZBcI7EH4A0O5dIhczo2N0AK9z495uSIv+0xUrSrDhFTZxZjo6gkADIXroRjP1kwqK6ew==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.808.0", + "@aws-sdk/middleware-sdk-s3": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", @@ -1089,12 +1089,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.808.0.tgz", - "integrity": "sha512-PsfKanHmnyO7FxowXqxbLQ+QjURCdSGxyhUiSdZbfvlvme/wqaMyIoMV/i4jppndksoSdPbW2kZXjzOqhQF+ew==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.810.0.tgz", + "integrity": "sha512-fdgHRCDpnzsD+0km7zuRbHRysJECfS8o9T9/pZ6XAr1z2FNV/UveHtnUYq0j6XpDMrIm0/suvXbshIjQU+a+sw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "3.808.0", + "@aws-sdk/nested-clients": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -1185,12 +1185,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.808.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.808.0.tgz", - "integrity": "sha512-5UmB6u7RBSinXZAVP2iDgqyeVA/odO2SLEcrXaeTCw8ICXEoqF0K+GL36T4iDbzCBOAIugOZ6OcQX5vH3ck5UA==", + "version": "3.810.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.810.0.tgz", + "integrity": "sha512-T56/ANEGNuvhqVoWZdr+0ZY2hjV93cH2OfGHIlVTVSAMACWG54XehDPESEso1CJNhJGYZPsE+FE42HGCk/XDMg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.808.0", + "@aws-sdk/middleware-user-agent": "3.810.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", @@ -1261,9 +1261,9 @@ } }, "node_modules/@datadog/native-iast-taint-tracking": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-3.3.1.tgz", - "integrity": "sha512-TgXpoX/CDgPfYAKu9qLmEyb9UXvRVC00D71islcSb70MCFmxQwkgXGl/gAk6YA6/NmZ4j8+cgY1lSNqStGvOMg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-4.0.0.tgz", + "integrity": "sha512-2uF8RnQkJO5bmLi26Zkhxg+RFJn/uEsesYTflScI/Cz/BWv+792bxI+OaCKvhgmpLkm8EElenlpidcJyZm7GYw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1285,9 +1285,9 @@ } }, "node_modules/@datadog/pprof": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@datadog/pprof/-/pprof-5.7.1.tgz", - "integrity": "sha512-D5XTxsaPG36x41vZZn8hsAeC7QQDx0rv1a1Uhxo5xCXUB/9rc19+I7iCnjgJS5aH0ShXdPVOWRClo16hOSKKSw==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@datadog/pprof/-/pprof-5.8.0.tgz", + "integrity": "sha512-3FL2qpkFWvIEptSk7x9RVs1PJeF+VCrGxQpKViloQkrnH5mjcwaIQiWyNZYyV1H1vhKJIS+ummSBcsOLkV49qA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -3138,12 +3138,12 @@ } }, "node_modules/@smithy/core": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.3.2.tgz", - "integrity": "sha512-GlLv+syoWolhtjX12XplL9BXBu10cjjD8iQC69fiKTrVNOB3Fjt8CVI9ccm6G3bLbMNe1gzrLD7yyMkYo4hchw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.3.3.tgz", + "integrity": "sha512-CiJNc0b/WdnttAfQ6uMkxPQ3Z8hG/ba8wF89x9KtBBLDdZk6CX52K4F8hbe94uNbc8LDUuZFtbqfdhM3T21naw==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.4", + "@smithy/middleware-serde": "^4.0.5", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", "@smithy/util-body-length-browser": "^4.0.0", @@ -3356,13 +3356,13 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.5.tgz", - "integrity": "sha512-WlpC9KVkajQf7RaGwi3n6lhHZzYTgm2PyX/2JjcwSHG417gFloNmYqN8rzDRXjT527/ZxZuvCsqq1gWZPW8lag==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.6.tgz", + "integrity": "sha512-Zdieg07c3ua3ap5ungdcyNnY1OsxmsXXtKDTk28+/YbwIPju0Z1ZX9X5AnkjmDE3+AbqgvhtC/ZuCMSr6VSfPw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.3.2", - "@smithy/middleware-serde": "^4.0.4", + "@smithy/core": "^3.3.3", + "@smithy/middleware-serde": "^4.0.5", "@smithy/node-config-provider": "^4.1.1", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", @@ -3375,15 +3375,15 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.6.tgz", - "integrity": "sha512-bl8q95nvCf7d22spxsBfs2giUDFf7prWLAxF5tmfgGBYHbUNW+OfnwMnabC15GMLA2AoE4HOtQR18a59lx6Blw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.7.tgz", + "integrity": "sha512-lFIFUJ0E/4I0UaIDY5usNUzNKAghhxO0lDH4TZktXMmE+e4ActD9F154Si0Unc01aCPzcwd+NcOwQw6AfXXRRQ==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.1.1", "@smithy/protocol-http": "^5.1.0", "@smithy/service-error-classification": "^4.0.3", - "@smithy/smithy-client": "^4.2.5", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.3", @@ -3408,9 +3408,9 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.4.tgz", - "integrity": "sha512-CaLvBtz+Xgs7eOwoinTXhZ02/9u8b28RT8lQAaDh7Q59nygeYYp1UiJjwl6zsay+lp0qVT/S7qLVI5RgcxjyfQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.5.tgz", + "integrity": "sha512-yREC3q/HXqQigq29xX3hiy6tFi+kjPKXoYUQmwQdgPORLbQ0n6V2Z/Iw9Nnlu66da9fM/WhDtGvYvqwecrCljQ==", "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.1.0", @@ -3563,13 +3563,13 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.5.tgz", - "integrity": "sha512-T3gA/TShe52Ln0ywWGVoDiqRvaxqvrU0CKRRmzT71/I1rRBD8mY85rvMMME6vw5RpBLJC9ADmXSLmpohF7RRhA==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.6.tgz", + "integrity": "sha512-WEqP0wQ1N/lVS4pwNK1Vk+0i6QIr66cq/xbu1dVy1tM0A0qYwAYyz0JhbquzM5pMa8s89lyDBtoGKxo7iG74GA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.3.2", - "@smithy/middleware-endpoint": "^4.1.5", + "@smithy/core": "^3.3.3", + "@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-stack": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/types": "^4.2.0", @@ -3670,13 +3670,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.13.tgz", - "integrity": "sha512-HCLfXAyTEpVWLuyxDABg8UQukeRwChL1UErpSQ4KJK2ZoadmXuQY68pTL9KcuEtasTkIjnzyLUL9vhLdJ3VFHQ==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.14.tgz", + "integrity": "sha512-l7QnMX8VcDOH6n/fBRu4zqguSlOBZxFzWqp58dXFSARFBjNlmEDk5G/z4T7BMGr+rI0Pg8MkhmMUfEtHFgpy2g==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.5", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -3686,16 +3686,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.13.tgz", - "integrity": "sha512-lu8E2RyzKzzFbNu4ICmY/2HltMZlJxMNg3saJ+r8I9vWbWbwdX7GOWUJdP4fbjEOm6aa52mnnd+uIRrT3dNEyA==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.14.tgz", + "integrity": "sha512-Ujs1gsWDo3m/T63VWBTBmHLTD2UlU6J6FEokLCEp7OZQv45jcjLHoxTwgWsi8ULpsYozvH4MTWkRP+bhwr0vDg==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^4.1.2", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/node-config-provider": "^4.1.1", "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.5", + "@smithy/smithy-client": "^4.2.6", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, @@ -5791,17 +5791,17 @@ } }, "node_modules/dd-trace": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.51.0.tgz", - "integrity": "sha512-/LH1RAoTau2KileM5mx58oGPBhdjejCYMqSMbbe0Rcqkipn/9RaxNMePGcfrnEa7Rwmar8PjPfDM5m7nn2d+Aw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.52.0.tgz", + "integrity": "sha512-ZF+OWLMcgVUWJEAIYIl76LocgnbbkPJ6WgJCG1fhLk4UCsUvoHRvBx9qlexbytL0jkktk1pvzODcjL0wyxLAOQ==", "hasInstallScript": true, "license": "(Apache-2.0 OR BSD-3-Clause)", "dependencies": { "@datadog/libdatadog": "^0.5.1", "@datadog/native-appsec": "8.5.2", - "@datadog/native-iast-taint-tracking": "3.3.1", + "@datadog/native-iast-taint-tracking": "4.0.0", "@datadog/native-metrics": "^3.1.1", - "@datadog/pprof": "5.7.1", + "@datadog/pprof": "5.8.0", "@datadog/sketches-js": "^2.1.0", "@datadog/wasm-js-rewriter": "4.0.1", "@isaacs/ttlcache": "^1.4.1", diff --git a/package.json b/package.json index ce5dcaa0c..bc550232b 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,14 @@ "job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.808.0", - "@aws-sdk/client-elasticache": "^3.808.0", - "@aws-sdk/client-s3": "^3.808.0", - "@aws-sdk/client-secrets-manager": "^3.808.0", - "@aws-sdk/client-ses": "^3.808.0", - "@aws-sdk/credential-provider-node": "^3.808.0", - "@aws-sdk/lib-storage": "^3.808.0", - "@aws-sdk/s3-request-presigner": "^3.808.0", + "@aws-sdk/client-cloudwatch-logs": "^3.810.0", + "@aws-sdk/client-elasticache": "^3.810.0", + "@aws-sdk/client-s3": "^3.810.0", + "@aws-sdk/client-secrets-manager": "^3.810.0", + "@aws-sdk/client-ses": "^3.810.0", + "@aws-sdk/credential-provider-node": "^3.810.0", + "@aws-sdk/lib-storage": "^3.810.0", + "@aws-sdk/s3-request-presigner": "^3.810.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -36,9 +36,9 @@ "cloudinary": "^2.6.1", "compression": "^1.8.0", "cookie-parser": "^1.4.7", - "cors": "2.8.5", + "cors": "^2.8.5", "crisp-status-reporter": "^1.2.2", - "dd-trace": "^5.51.0", + "dd-trace": "^5.52.0", "dinero.js": "^1.9.1", "dotenv": "^16.4.5", "express": "^4.21.1", From f770b2f1b1fde045c3f35d721f4698e5bde7f15f Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 19 May 2025 12:28:50 -0700 Subject: [PATCH 08/22] IO-3105 Add QBO Minor Version. --- server/accounting/qbo/qbo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/accounting/qbo/qbo.js b/server/accounting/qbo/qbo.js index 5c7e9d874..9abce0805 100644 --- a/server/accounting/qbo/qbo.js +++ b/server/accounting/qbo/qbo.js @@ -6,7 +6,7 @@ require("dotenv").config({ function urlBuilder(realmId, object, query = null) { return `https://${ process.env.NODE_ENV === "production" ? "" : "sandbox-" - }quickbooks.api.intuit.com/v3/company/${realmId}/${object}${query ? `?query=${encodeURIComponent(query)}` : ""}`; + }quickbooks.api.intuit.com/v3/company/${realmId}/${object}?minorversion=75${query ? `&query=${encodeURIComponent(query)}` : ""}`; } function StandardizeName(str) { From 6ad9e27d1d1e72bf87b9f6c7dc6ff2e986833b80 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 May 2025 12:38:31 -0400 Subject: [PATCH 09/22] feature/IO-3182-Phone-Number-Consent - Merge master / bump deps --- client/package-lock.json | 357 +++++------ client/package.json | 24 +- package-lock.json | 1255 +++++++++++++------------------------- package.json | 24 +- 4 files changed, 586 insertions(+), 1074 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 496885fb9..2939dbd7c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -13,19 +13,19 @@ "@apollo/client": "^3.13.6", "@emotion/is-prop-valid": "^1.3.1", "@fingerprintjs/fingerprintjs": "^4.6.1", - "@firebase/analytics": "^0.10.13", - "@firebase/app": "^0.12.1", - "@firebase/auth": "^1.10.4", - "@firebase/firestore": "^4.7.14", - "@firebase/messaging": "^0.12.18", + "@firebase/analytics": "^0.10.16", + "@firebase/app": "^0.13.0", + "@firebase/auth": "^1.10.5", + "@firebase/firestore": "^4.7.15", + "@firebase/messaging": "^0.12.21", "@jsreport/browser-client": "^3.1.0", "@reduxjs/toolkit": "^2.8.2", "@sentry/cli": "^2.45.0", - "@sentry/react": "^9.19.0", + "@sentry/react": "^9.21.0", "@sentry/vite-plugin": "^3.4.0", "@splitsoftware/splitio-react": "^2.1.1", "@tanem/react-nprogress": "^5.0.53", - "antd": "^5.25.1", + "antd": "^5.25.2", "apollo-link-logger": "^2.0.1", "apollo-link-sentry": "^4.3.0", "autosize": "^6.0.1", @@ -49,7 +49,7 @@ "normalize-url": "^8.0.1", "object-hash": "^3.0.0", "prop-types": "^15.8.1", - "query-string": "^9.1.2", + "query-string": "^9.2.0", "raf-schd": "^4.0.3", "react": "^18.3.1", "react-big-calendar": "^1.18.0", @@ -78,7 +78,7 @@ "redux-saga": "^1.3.0", "redux-state-sync": "^3.1.4", "reselect": "^5.1.1", - "sass": "^1.88.0", + "sass": "^1.89.0", "socket.io-client": "^4.8.1", "styled-components": "^6.1.18", "subscriptions-transport-ws": "^0.11.0", @@ -90,10 +90,10 @@ "@ant-design/icons": "^6.0.0", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.27.1", - "@dotenvx/dotenvx": "^1.44.0", + "@dotenvx/dotenvx": "^1.44.1", "@emotion/babel-plugin": "^11.13.5", "@emotion/react": "^11.14.0", - "@eslint/js": "^9.26.0", + "@eslint/js": "^9.27.0", "@playwright/test": "^1.51.1", "@sentry/webpack-plugin": "^3.4.0", "@testing-library/dom": "^10.4.0", @@ -120,7 +120,7 @@ "vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-pwa": "^1.0.0", "vite-plugin-style-import": "^2.0.0", - "vitest": "^3.1.3", + "vitest": "^3.1.4", "workbox-window": "^7.3.0" }, "engines": { @@ -2587,9 +2587,9 @@ } }, "node_modules/@dotenvx/dotenvx": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.44.0.tgz", - "integrity": "sha512-18Aa+7KP/L2Kj9lxmT4EJZnsCq/xGIHgzU26rdzsKMhjpeT3YY+qin/dNAnIaVHPZnee7kXpZL55M9htd30r7Q==", + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.44.1.tgz", + "integrity": "sha512-j1QImCqf/XJmhIjC1OPpgiZV9g370HG9MNT9s/CDwCKsoYzNCPEKK+GfsidahJx7yIlBbm+4dPLlGec+bKn7oA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2912,13 +2912,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", - "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@fingerprintjs/fingerprintjs": { @@ -2931,15 +2934,15 @@ } }, "node_modules/@firebase/analytics": { - "version": "0.10.13", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.13.tgz", - "integrity": "sha512-X+6wMOPgA9l0AeeMdMcMfaCP4XKPvrhx55MGuMrfHvUrOvFKldpzBum7KkoGJMoexKmqmKP+mCmJMY9Fb8K6Hw==", + "version": "0.10.16", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.16.tgz", + "integrity": "sha512-cMtp19He7Fd6uaj/nDEul+8JwvJsN8aRSJyuA1QN3QrKvfDDp+efjVurJO61sJpkVftw9O9nNMdhFbRcTmTfRQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.14", - "@firebase/installations": "0.6.14", + "@firebase/component": "0.6.17", + "@firebase/installations": "0.6.17", "@firebase/logger": "0.4.4", - "@firebase/util": "1.11.1", + "@firebase/util": "1.12.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2947,14 +2950,14 @@ } }, "node_modules/@firebase/app": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.12.1.tgz", - "integrity": "sha512-ASExOlmmjRMdwOQ65Oj6R9JBqa7iiT1/LgZjtbU7FqxoJZNWHrt39NJ/z2bjyYDdAHX8jkY7muFqzahScCXgfA==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.0.tgz", + "integrity": "sha512-Vj3MST245nq+V5UmmfEkB3isIgPouyUr8yGJlFeL9Trg/umG5ogAvrjAYvQ8gV7daKDoQSRnJKWI2JFpQqRsuQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.14", + "@firebase/component": "0.6.17", "@firebase/logger": "0.4.4", - "@firebase/util": "1.11.1", + "@firebase/util": "1.12.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -2963,14 +2966,14 @@ } }, "node_modules/@firebase/auth": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.4.tgz", - "integrity": "sha512-rZQZQkn5x7BcHenYJi9RYWoOMJHdM/CsF6DMclb/CKbntzjUaZj+R45Iyzf/BFUJ9L2sA4bNPhJK9x+l9VKvLQ==", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.5.tgz", + "integrity": "sha512-6wF/NdMTwObL4RNQePunuzMr9O3gyftisvFZFFKf57D2HONXo87YymogRV8d+Z7SLA0rcNBN1gLJVk2D0y97gA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.16", + "@firebase/component": "0.6.17", "@firebase/logger": "0.4.4", - "@firebase/util": "1.11.3", + "@firebase/util": "1.12.0", "tslib": "^2.1.0" }, "engines": { @@ -2986,39 +2989,13 @@ } } }, - "node_modules/@firebase/auth/node_modules/@firebase/component": { - "version": "0.6.16", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.16.tgz", - "integrity": "sha512-whx+e3pgC3J9O6t4LOB8jiLk3tpWtnXaQ+xt/ys/4IGUPRI+nnWooVdtWrEnMga/gT03ug9SdEAEJLl6I1BIlg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/util": "1.11.3", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@firebase/auth/node_modules/@firebase/util": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.3.tgz", - "integrity": "sha512-4wYnOV9FpwdCq3rHQOCrdx4AQBUbfH1p2DhWGQxlQ+D3Xl/wSxc/HttcyPN4NNFiynxoNCFGWQH/zdhRfxP1Zg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@firebase/component": { - "version": "0.6.14", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.14.tgz", - "integrity": "sha512-kf/zAT8GQJ9nYoHuj0mv7twp1QzifKYrO+GsmsVHHM+Hi9KkmI7E3B3J0CtihHpb34vinl4gbJrYJ2p2wfvc9A==", + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.17.tgz", + "integrity": "sha512-M6DOg7OySrKEFS8kxA3MU5/xc37fiOpKPMz6cTsMUcsuKB6CiZxxNAvgFta8HGRgEpZbi8WjGIj6Uf+TpOhyzg==", "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.11.1", + "@firebase/util": "1.12.0", "tslib": "^2.1.0" }, "engines": { @@ -3026,14 +3003,14 @@ } }, "node_modules/@firebase/firestore": { - "version": "4.7.14", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.14.tgz", - "integrity": "sha512-YLz71p96ACfILNjnqh7H6ilsT3AZZyDpCCE+wpl8mJklAbdpyd2ahNIqS1eBCjseqls8vQO/XTaIcbpkSgQFIg==", + "version": "4.7.15", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.15.tgz", + "integrity": "sha512-FgWTmkNBEXdKCoN2ngBNjrMaXuBx6QwjiZZVnOGg+VjUmiBq5gAqlDIW5bZY6i/NYvLUrWugdqIs7y9GHEqwww==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.16", + "@firebase/component": "0.6.17", "@firebase/logger": "0.4.4", - "@firebase/util": "1.11.3", + "@firebase/util": "1.12.0", "@firebase/webchannel-wrapper": "1.0.3", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", @@ -3046,40 +3023,14 @@ "@firebase/app": "0.x" } }, - "node_modules/@firebase/firestore/node_modules/@firebase/component": { - "version": "0.6.16", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.16.tgz", - "integrity": "sha512-whx+e3pgC3J9O6t4LOB8jiLk3tpWtnXaQ+xt/ys/4IGUPRI+nnWooVdtWrEnMga/gT03ug9SdEAEJLl6I1BIlg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/util": "1.11.3", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@firebase/firestore/node_modules/@firebase/util": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.3.tgz", - "integrity": "sha512-4wYnOV9FpwdCq3rHQOCrdx4AQBUbfH1p2DhWGQxlQ+D3Xl/wSxc/HttcyPN4NNFiynxoNCFGWQH/zdhRfxP1Zg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@firebase/installations": { - "version": "0.6.14", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.14.tgz", - "integrity": "sha512-uE837g9+sv6PfjWPgOfG3JtjZ+hJ7KBHO4UVenVsvhzgOxFkvLjO/bgE7fyvsaD3fOHSXunx3adRIg4eUEMPyA==", + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.17.tgz", + "integrity": "sha512-zfhqCNJZRe12KyADtRrtOj+SeSbD1H/K8J24oQAJVv/u02eQajEGlhZtcx9Qk7vhGWF5z9dvIygVDYqLL4o1XQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.14", - "@firebase/util": "1.11.1", + "@firebase/component": "0.6.17", + "@firebase/util": "1.12.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -3100,15 +3051,15 @@ } }, "node_modules/@firebase/messaging": { - "version": "0.12.18", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.18.tgz", - "integrity": "sha512-2MGhUGoCZloB7ysoYzG/T2nnRmHYLT+AcqYouZuD6APabpkDhF8lHsmSQq4MFSlXhI3DKFOXxjuvbY8ec4C2JQ==", + "version": "0.12.21", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.21.tgz", + "integrity": "sha512-bYJ2Evj167Z+lJ1ach6UglXz5dUKY1zrJZd15GagBUJSR7d9KfiM1W8dsyL0lDxcmhmA/sLaBYAAhF1uilwN0g==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.14", - "@firebase/installations": "0.6.14", + "@firebase/component": "0.6.17", + "@firebase/installations": "0.6.17", "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.11.1", + "@firebase/util": "1.12.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -3123,9 +3074,9 @@ "license": "Apache-2.0" }, "node_modules/@firebase/util": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.1.tgz", - "integrity": "sha512-RXg4WE8C2LUrvoV/TMGRTu223zZf9Dq9MR8yHZio9nF9TpLnpCPURw9VWWB2WATDl6HfIdWfl2x2SJYtHkN4hw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.0.tgz", + "integrity": "sha512-Z4rK23xBCwgKDqmzGVMef+Vb4xso2j5Q8OG0vVL4m4fA5ZjPMYQazu8OJJC3vtQRC3SQ/Pgx/6TPNVsCd70QRw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4510,50 +4461,50 @@ "license": "MIT" }, "node_modules/@sentry-internal/browser-utils": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.19.0.tgz", - "integrity": "sha512-DlEHX4eIHe5yIuh/cFu9OiaFuk1CTnFK95zj61I7Q2fxmN43dIwC3xAAGJ/Hy+GDQi7kU+BiS2sudSHSTq81BA==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.21.0.tgz", + "integrity": "sha512-/lJ5EVUDbsVsPH/sSXwWBERVtzi4kWYeFLc+u+1zr4NrfDrGnPJ5mVS1VlHwtBmYIIWv8harLP+CReg3nDcXdw==", "license": "MIT", "dependencies": { - "@sentry/core": "9.19.0" + "@sentry/core": "9.21.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/feedback": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.19.0.tgz", - "integrity": "sha512-yixRrv4NfpjhFW56AuUTjVwZlignB9FWAXXyrmRP3SsFeJCFrAsSD8HOxV9RXNr9ePYl7MEU0Agi43YWhJsiAw==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.21.0.tgz", + "integrity": "sha512-Z234NgcWolFpmztCh+9smC6WlO8By5t4KucHNfYSQ0xQYQCxPL5iChj3JpF4dwv+qCYXhDFLQFQbK0U3Px056g==", "license": "MIT", "dependencies": { - "@sentry/core": "9.19.0" + "@sentry/core": "9.21.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.19.0.tgz", - "integrity": "sha512-i/X9brRchbAF25yjxLTI7E8eoESRPBgIyQOWoWRXXt2n51iBRTjLXSaEfGvjdN+qrMq/yd6nC1/UqJVxXHeIhA==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.21.0.tgz", + "integrity": "sha512-7mq3Bsp8EJa3YTIYgmWfNgJdvbeaAJ6VYsqi0yxR/vNGxY3qH+PLlv+ZOEXI2U0CL6vhqFPbqmxiUOCuAjnpGg==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.19.0", - "@sentry/core": "9.19.0" + "@sentry-internal/browser-utils": "9.21.0", + "@sentry/core": "9.21.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.19.0.tgz", - "integrity": "sha512-YC8yrOjuKSfQgGniJnzkdbFsWEPTlNpzeeYPTxS4ouH1FwfGrSkPmcddjor2YHaLfiuHHqQ/Vvq70n+zruJH7A==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.21.0.tgz", + "integrity": "sha512-4tHiNil8qXphaql2YXLGA/wlm0hxaadrh7x8/KErn1iy3vJpn7t/Kka5uug7c2UWhtveS6dgGmqjSkDxM5h9bA==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "9.19.0", - "@sentry/core": "9.19.0" + "@sentry-internal/replay": "9.21.0", + "@sentry/core": "9.21.0" }, "engines": { "node": ">=18" @@ -4569,16 +4520,16 @@ } }, "node_modules/@sentry/browser": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.19.0.tgz", - "integrity": "sha512-efKfPQ0yQkdIkC7qJ5TIHxnecLNENGUYl1YD/TC8yyzW2JRf/3OYo5yg1hY2rhsP5RwQShXlT7uA03ABVIkA4A==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.21.0.tgz", + "integrity": "sha512-NF0G104JRP2TZ2hpMHElO4bEEUdBWknKSh2d0SRyGpJFVfOQG3oRHczXWH08A5InA/lNrS9LEdodUhiFue+F3A==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.19.0", - "@sentry-internal/feedback": "9.19.0", - "@sentry-internal/replay": "9.19.0", - "@sentry-internal/replay-canvas": "9.19.0", - "@sentry/core": "9.19.0" + "@sentry-internal/browser-utils": "9.21.0", + "@sentry-internal/feedback": "9.21.0", + "@sentry-internal/replay": "9.21.0", + "@sentry-internal/replay-canvas": "9.21.0", + "@sentry/core": "9.21.0" }, "engines": { "node": ">=18" @@ -4951,22 +4902,22 @@ } }, "node_modules/@sentry/core": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.19.0.tgz", - "integrity": "sha512-I41rKpMJHHZb0z0Nja+Lxto6IkEEmX3uWjnECypF8Z1HIjeJB0+PXl8p/7TeaKYqw2J2GYcRTg7jQZDmvKle1w==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.21.0.tgz", + "integrity": "sha512-K0a72Evg0fzc52Oe8R8Op5TyUMzORkk4ytt3G24lSnF4hh8NPf0m6VGkEUgQRPj27g2bF6tq9fCNsJILsf1PDA==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@sentry/react": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.19.0.tgz", - "integrity": "sha512-tHuzPVbqKsONlFQsy7FqqGjBaujQoLRIDBLlPPMNoiGvP3rodBl6t1v5zoNAq4m47i3MhvpLEYf6C00j1w5UMQ==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.21.0.tgz", + "integrity": "sha512-RGbyVo4fS7SX2AjEpdRXDo4C4IYIx0zQcI5bSTgySuhxL0JAxohcuSsNWpx48QkJwK/avtmlmCIPKgbvhF16TQ==", "license": "MIT", "dependencies": { - "@sentry/browser": "9.19.0", - "@sentry/core": "9.19.0", + "@sentry/browser": "9.21.0", + "@sentry/core": "9.21.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -5862,14 +5813,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz", - "integrity": "sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz", + "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -5878,13 +5829,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.3.tgz", - "integrity": "sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz", + "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.3", + "@vitest/spy": "3.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -5925,9 +5876,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", - "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", + "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", "dev": true, "license": "MIT", "dependencies": { @@ -5938,13 +5889,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.3.tgz", - "integrity": "sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz", + "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.1.3", + "@vitest/utils": "3.1.4", "pathe": "^2.0.3" }, "funding": { @@ -5959,13 +5910,13 @@ "license": "MIT" }, "node_modules/@vitest/snapshot": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.3.tgz", - "integrity": "sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz", + "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.3", + "@vitest/pretty-format": "3.1.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -5991,9 +5942,9 @@ "license": "MIT" }, "node_modules/@vitest/spy": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz", - "integrity": "sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz", + "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -6004,13 +5955,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.3.tgz", - "integrity": "sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.3", + "@vitest/pretty-format": "3.1.4", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -6140,9 +6091,9 @@ } }, "node_modules/antd": { - "version": "5.25.1", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.25.1.tgz", - "integrity": "sha512-4KC7KuPCjr0z3Vuw9DsF+ceqJaPLbuUI3lOX1sY8ix25ceamp+P8yxOmk3Y2JHCD2ZAhq+5IQ/DTJRN2adWYKQ==", + "version": "5.25.2", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.25.2.tgz", + "integrity": "sha512-7R2nUvlHhey7Trx64+hCtGXOiy+DTUs1Lv5bwbV1LzEIZIhWb0at1AM6V3K108a5lyoR9n7DX3ptlLF7uYV/DQ==", "license": "MIT", "dependencies": { "@ant-design/colors": "^7.2.0", @@ -6180,11 +6131,11 @@ "rc-rate": "~2.13.1", "rc-resize-observer": "^1.4.3", "rc-segmented": "~2.7.0", - "rc-select": "~14.16.7", + "rc-select": "~14.16.8", "rc-slider": "~11.1.8", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.50.4", + "rc-table": "~7.50.5", "rc-tabs": "~15.6.1", "rc-textarea": "~1.10.0", "rc-tooltip": "~6.4.0", @@ -13621,9 +13572,9 @@ } }, "node_modules/query-string": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.1.2.tgz", - "integrity": "sha512-s3UlTyjxRux4KjwWaJsjh1Mp8zoCkSGKirbD9H89pEM9UOZsfpRZpdfzvsy2/mGlLfC3NnYVpy2gk7jXITHEtA==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.2.0.tgz", + "integrity": "sha512-YIRhrHujoQxhexwRLxfy3VSjOXmvZRd2nyw1PwL1UUqZ/ys1dEZd1+NSgXkne2l/4X/7OXkigEAuhTX0g/ivJQ==", "license": "MIT", "dependencies": { "decode-uri-component": "^0.4.1", @@ -14076,9 +14027,9 @@ } }, "node_modules/rc-select": { - "version": "14.16.7", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.7.tgz", - "integrity": "sha512-lT9kO5gFHQdJzu9a0btcOtNaJHkhenSl8H5mcpgXN9VIMXP59rnkpbdHmPrteixWs1D5zFOTyoTYX3b7joADIQ==", + "version": "14.16.8", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -14149,9 +14100,9 @@ } }, "node_modules/rc-table": { - "version": "7.50.4", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.4.tgz", - "integrity": "sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==", + "version": "7.50.5", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.5.tgz", + "integrity": "sha512-FDZu8aolhSYd3v9KOc3lZOVAU77wmRRu44R0Wfb8Oj1dXRUsloFaXMSl6f7yuWZUxArJTli7k8TEOX2mvhDl4A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", @@ -15427,9 +15378,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.88.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz", - "integrity": "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==", + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.0.tgz", + "integrity": "sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==", "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -17585,9 +17536,9 @@ } }, "node_modules/vite-node": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.3.tgz", - "integrity": "sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz", + "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==", "dev": true, "license": "MIT", "dependencies": { @@ -17783,19 +17734,19 @@ } }, "node_modules/vitest": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz", - "integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz", + "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.1.3", - "@vitest/mocker": "3.1.3", - "@vitest/pretty-format": "^3.1.3", - "@vitest/runner": "3.1.3", - "@vitest/snapshot": "3.1.3", - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", + "@vitest/expect": "3.1.4", + "@vitest/mocker": "3.1.4", + "@vitest/pretty-format": "^3.1.4", + "@vitest/runner": "3.1.4", + "@vitest/snapshot": "3.1.4", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.2.1", @@ -17808,7 +17759,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.3", + "vite-node": "3.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -17824,8 +17775,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.3", - "@vitest/ui": "3.1.3", + "@vitest/browser": "3.1.4", + "@vitest/ui": "3.1.4", "happy-dom": "*", "jsdom": "*" }, diff --git a/client/package.json b/client/package.json index 4018d9629..c6bc4f515 100644 --- a/client/package.json +++ b/client/package.json @@ -12,19 +12,19 @@ "@apollo/client": "^3.13.6", "@emotion/is-prop-valid": "^1.3.1", "@fingerprintjs/fingerprintjs": "^4.6.1", - "@firebase/analytics": "^0.10.13", - "@firebase/app": "^0.12.1", - "@firebase/auth": "^1.10.4", - "@firebase/firestore": "^4.7.14", - "@firebase/messaging": "^0.12.18", + "@firebase/analytics": "^0.10.16", + "@firebase/app": "^0.13.0", + "@firebase/auth": "^1.10.5", + "@firebase/firestore": "^4.7.15", + "@firebase/messaging": "^0.12.21", "@jsreport/browser-client": "^3.1.0", "@reduxjs/toolkit": "^2.8.2", "@sentry/cli": "^2.45.0", - "@sentry/react": "^9.19.0", + "@sentry/react": "^9.21.0", "@sentry/vite-plugin": "^3.4.0", "@splitsoftware/splitio-react": "^2.1.1", "@tanem/react-nprogress": "^5.0.53", - "antd": "^5.25.1", + "antd": "^5.25.2", "apollo-link-logger": "^2.0.1", "apollo-link-sentry": "^4.3.0", "autosize": "^6.0.1", @@ -48,7 +48,7 @@ "normalize-url": "^8.0.1", "object-hash": "^3.0.0", "prop-types": "^15.8.1", - "query-string": "^9.1.2", + "query-string": "^9.2.0", "raf-schd": "^4.0.3", "react": "^18.3.1", "react-big-calendar": "^1.18.0", @@ -77,7 +77,7 @@ "redux-saga": "^1.3.0", "redux-state-sync": "^3.1.4", "reselect": "^5.1.1", - "sass": "^1.88.0", + "sass": "^1.89.0", "socket.io-client": "^4.8.1", "styled-components": "^6.1.18", "subscriptions-transport-ws": "^0.11.0", @@ -130,10 +130,10 @@ "@ant-design/icons": "^6.0.0", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.27.1", - "@dotenvx/dotenvx": "^1.44.0", + "@dotenvx/dotenvx": "^1.44.1", "@emotion/babel-plugin": "^11.13.5", "@emotion/react": "^11.14.0", - "@eslint/js": "^9.26.0", + "@eslint/js": "^9.27.0", "@playwright/test": "^1.51.1", "@sentry/webpack-plugin": "^3.4.0", "@testing-library/dom": "^10.4.0", @@ -160,7 +160,7 @@ "vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-pwa": "^1.0.0", "vite-plugin-style-import": "^2.0.0", - "vitest": "^3.1.3", + "vitest": "^3.1.4", "workbox-window": "^7.3.0" } } diff --git a/package-lock.json b/package-lock.json index 807f273d1..8e0440f9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,14 @@ "version": "0.2.0", "license": "UNLICENSED", "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.810.0", - "@aws-sdk/client-elasticache": "^3.810.0", - "@aws-sdk/client-s3": "^3.810.0", - "@aws-sdk/client-secrets-manager": "^3.810.0", - "@aws-sdk/client-ses": "^3.810.0", - "@aws-sdk/credential-provider-node": "^3.810.0", - "@aws-sdk/lib-storage": "^3.810.0", - "@aws-sdk/s3-request-presigner": "^3.810.0", + "@aws-sdk/client-cloudwatch-logs": "^3.812.0", + "@aws-sdk/client-elasticache": "^3.812.0", + "@aws-sdk/client-s3": "^3.812.0", + "@aws-sdk/client-secrets-manager": "^3.812.0", + "@aws-sdk/client-ses": "^3.812.0", + "@aws-sdk/credential-provider-node": "^3.812.0", + "@aws-sdk/lib-storage": "^3.812.0", + "@aws-sdk/s3-request-presigner": "^3.812.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -24,7 +24,7 @@ "aws4": "^1.13.2", "axios": "^1.8.4", "better-queue": "^3.8.12", - "bullmq": "^5.52.2", + "bullmq": "^5.52.3", "chart.js": "^4.4.8", "cloudinary": "^2.6.1", "compression": "^1.8.0", @@ -66,15 +66,15 @@ "xmlbuilder2": "^3.1.1" }, "devDependencies": { - "@eslint/js": "^9.26.0", - "eslint": "^9.26.0", + "@eslint/js": "^9.27.0", + "eslint": "^9.27.0", "eslint-plugin-react": "^7.37.5", "globals": "^15.15.0", "mock-require": "^3.0.3", "p-limit": "^3.1.0", "prettier": "^3.5.3", "supertest": "^7.1.1", - "vitest": "^3.1.3" + "vitest": "^3.1.4" }, "engines": { "node": ">=22.13.0", @@ -284,24 +284,24 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.810.0.tgz", - "integrity": "sha512-1FLkuei0pnBIGJJktYhY03LNcdGiYyxn8TcugUl4A3+7fOs9HCmcA+a5vTUThvnDqFKn4xtptQgYo4oiSLxtwA==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.812.0.tgz", + "integrity": "sha512-SLvqaMwRviAwb+z4XAq2QmlbUjr7rXN6zAEr4/x2ltyrsxEV95gBo0KHeroAsWhd4eD19USjAgg64KJgvUtNGw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.810.0", - "@aws-sdk/credential-provider-node": "3.810.0", + "@aws-sdk/core": "3.812.0", + "@aws-sdk/credential-provider-node": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.810.0", + "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.810.0", + "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/eventstream-serde-browser": "^4.0.2", @@ -352,24 +352,24 @@ } }, "node_modules/@aws-sdk/client-elasticache": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.810.0.tgz", - "integrity": "sha512-tB7bBIoAz+qoFq8b1oR6TdjIru3Ui9VL+TWzM6jxxgS2LtyH3MS6hu0qsJWIVeDZO9MEOdb66u+aQoyxJecasA==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.812.0.tgz", + "integrity": "sha512-o1KC5Glo3c0T/RN2XBanHu40k3M99MJyq+e/02tIMgEGKIPmnvB8A8muE2F3rQ2A0qLCxvjhm+kprlmDwzpryw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.810.0", - "@aws-sdk/credential-provider-node": "3.810.0", + "@aws-sdk/core": "3.812.0", + "@aws-sdk/credential-provider-node": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.810.0", + "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.810.0", + "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -403,32 +403,32 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.810.0.tgz", - "integrity": "sha512-wM8M0BrqRkbZ9fGaLmAl24CUgVmmLjiKuNTqGOHsdCIc7RV+IGv5CnGK7ciOltAttrFBxvDlhy2lg5F8gNw8Bg==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.812.0.tgz", + "integrity": "sha512-kHgw9JDXNPLa/mHtWpOd5btBVXFSe+wwp1Ed9+bqz9uLkv0iV4joZrdQwnydkO8zlTs60Sc5ez+P2OiZ76i2Qg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.810.0", - "@aws-sdk/credential-provider-node": "3.810.0", + "@aws-sdk/core": "3.812.0", + "@aws-sdk/credential-provider-node": "3.812.0", "@aws-sdk/middleware-bucket-endpoint": "3.808.0", "@aws-sdk/middleware-expect-continue": "3.804.0", - "@aws-sdk/middleware-flexible-checksums": "3.810.0", + "@aws-sdk/middleware-flexible-checksums": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-location-constraint": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-sdk-s3": "3.810.0", + "@aws-sdk/middleware-sdk-s3": "3.812.0", "@aws-sdk/middleware-ssec": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.810.0", + "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", - "@aws-sdk/signature-v4-multi-region": "3.810.0", + "@aws-sdk/signature-v4-multi-region": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.810.0", + "@aws-sdk/util-user-agent-node": "3.812.0", "@aws-sdk/xml-builder": "3.804.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", @@ -470,24 +470,24 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.810.0.tgz", - "integrity": "sha512-FKqvgtBOQ69SXFfuMpgx81GmaJMAmCyfb6JjSdSay0Gj585dzb6Jn7ye8zkuy4k6XAt8flnbeaFNORbY9BT38g==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.812.0.tgz", + "integrity": "sha512-RyGzi7kkacjPd0QgVjw6OYvZVvuqtd1wRwG0Aek32dPUYu8eOs9FDaqBsDnNIqdw+lAqC/pKIOPYWtLu2OxE0Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.810.0", - "@aws-sdk/credential-provider-node": "3.810.0", + "@aws-sdk/core": "3.812.0", + "@aws-sdk/credential-provider-node": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.810.0", + "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.810.0", + "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -535,24 +535,24 @@ } }, "node_modules/@aws-sdk/client-ses": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.810.0.tgz", - "integrity": "sha512-QUaW4bJIpqNyIPWvRun8avLDuIfbWOAyspgnG5JSkQtIK3wWeeeR8lXrCNFtgzY0X8MPdy0Ns50YbVEsVWnmMA==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.812.0.tgz", + "integrity": "sha512-7JUS2u0AKMYiEmRrxAYQj8ifFwVUgMAHt5H/KjMhh+1El0NqAQDt3JLD4Asmzy7/TvTAWZfk5np2LQPNB2wZpw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.810.0", - "@aws-sdk/credential-provider-node": "3.810.0", + "@aws-sdk/core": "3.812.0", + "@aws-sdk/credential-provider-node": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.810.0", + "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.810.0", + "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -586,23 +586,23 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.810.0.tgz", - "integrity": "sha512-Txp/3jHqkfA4BTklQEOGiZ1yTUxg+hITislfaWEzJ904vlDt4DvAljTlhfaz7pceCLA2+LhRlYZYSv7t5b0Ltw==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.812.0.tgz", + "integrity": "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.810.0", + "@aws-sdk/core": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.810.0", + "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.810.0", + "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -635,9 +635,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.810.0.tgz", - "integrity": "sha512-s2IJk+qa/15YZcv3pbdQNATDR+YdYnHf94MrAeVAWubtRLnzD8JciC+gh4LSPp7JzrWSvVOg2Ut1S+0y89xqCg==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.812.0.tgz", + "integrity": "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.804.0", @@ -657,12 +657,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.810.0.tgz", - "integrity": "sha512-iwHqF+KryKONfbdFk3iKhhPk4fHxh5QP5fXXR//jhYwmszaLOwc7CLCE9AxhgiMzAs+kV8nBFQZvdjFpPzVGOA==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.812.0.tgz", + "integrity": "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.810.0", + "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -673,12 +673,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.810.0.tgz", - "integrity": "sha512-SKzjLd+8ugif7yy9sOAAdnPE1vCBHQe6jKgs2AadMpCmWm34DiHz/KuulHdvURUGMIi7CvmaC8aH77twDPYbtg==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.812.0.tgz", + "integrity": "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.810.0", + "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", @@ -694,18 +694,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.810.0.tgz", - "integrity": "sha512-H2QCSnxWJ/mj8HTcyHmCmyQ5bO/+imRi4mlBIpUyKjiYKro52WD3gXlGgPIDo2q3UFIHq37kmYvS00i+qIY9tw==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.812.0.tgz", + "integrity": "sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.810.0", - "@aws-sdk/credential-provider-env": "3.810.0", - "@aws-sdk/credential-provider-http": "3.810.0", - "@aws-sdk/credential-provider-process": "3.810.0", - "@aws-sdk/credential-provider-sso": "3.810.0", - "@aws-sdk/credential-provider-web-identity": "3.810.0", - "@aws-sdk/nested-clients": "3.810.0", + "@aws-sdk/core": "3.812.0", + "@aws-sdk/credential-provider-env": "3.812.0", + "@aws-sdk/credential-provider-http": "3.812.0", + "@aws-sdk/credential-provider-process": "3.812.0", + "@aws-sdk/credential-provider-sso": "3.812.0", + "@aws-sdk/credential-provider-web-identity": "3.812.0", + "@aws-sdk/nested-clients": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", @@ -718,17 +718,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.810.0.tgz", - "integrity": "sha512-9E3Chv3x+RBM3N1bwLCyvXxoiPAckCI74wG7ePN4F3b/7ieIkbEl/3Hd67j1fnt62Xa1cjUHRu2tz5pdEv5G1Q==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.812.0.tgz", + "integrity": "sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.810.0", - "@aws-sdk/credential-provider-http": "3.810.0", - "@aws-sdk/credential-provider-ini": "3.810.0", - "@aws-sdk/credential-provider-process": "3.810.0", - "@aws-sdk/credential-provider-sso": "3.810.0", - "@aws-sdk/credential-provider-web-identity": "3.810.0", + "@aws-sdk/credential-provider-env": "3.812.0", + "@aws-sdk/credential-provider-http": "3.812.0", + "@aws-sdk/credential-provider-ini": "3.812.0", + "@aws-sdk/credential-provider-process": "3.812.0", + "@aws-sdk/credential-provider-sso": "3.812.0", + "@aws-sdk/credential-provider-web-identity": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/credential-provider-imds": "^4.0.4", "@smithy/property-provider": "^4.0.2", @@ -741,12 +741,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.810.0.tgz", - "integrity": "sha512-42kE6MLdsmMGp1id3Gisal4MbMiF7PIc0tAznTeIuE8r7cIF8yeQWw/PBOIvjyI57DxbyKzLUAMEJuigUpApCw==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.812.0.tgz", + "integrity": "sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.810.0", + "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -758,14 +758,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.810.0.tgz", - "integrity": "sha512-8WjX6tz+FCvM93Y33gsr13p/HiiTJmVn5AK1O8PTkvHBclQDzmtAW5FdPqTpAJGswLW2FB0xRqdsSMN2dQEjNw==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.812.0.tgz", + "integrity": "sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.810.0", - "@aws-sdk/core": "3.810.0", - "@aws-sdk/token-providers": "3.810.0", + "@aws-sdk/client-sso": "3.812.0", + "@aws-sdk/core": "3.812.0", + "@aws-sdk/token-providers": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -777,13 +777,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.810.0.tgz", - "integrity": "sha512-uKQJY0AcPyrvMmfGLo36semgjqJ4vmLTqOSW9u40qQDspRnG73/P09lAO2ntqKlhwvMBt3XfcNnOpyyhKRcOfA==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.812.0.tgz", + "integrity": "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.810.0", - "@aws-sdk/nested-clients": "3.810.0", + "@aws-sdk/core": "3.812.0", + "@aws-sdk/nested-clients": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", @@ -794,9 +794,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.810.0.tgz", - "integrity": "sha512-Ri3Kgkk9XK7wmgKaQUA4ZNgr4lTASFlt2pd4xo7sbpX7K984fYkWe7ecrgbRptIiIYUjeJiYCRb0FS34diSaZw==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.812.0.tgz", + "integrity": "sha512-z37ykuXQXfGO7dqQFbEnj1Wu9UwUUXpZhr4iWXsehbIzSqyl5FiCMp0cI5XK8jLVACCfSCssZCz6QD4oDYdKlQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.0.2", @@ -811,7 +811,7 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.810.0" + "@aws-sdk/client-s3": "^3.812.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { @@ -848,15 +848,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.810.0.tgz", - "integrity": "sha512-lF5fse+26hluElOtDZMsi5EH50G13OEqglFgpSc6xWnqNhbDc+CnPQRMwTVlOJBDR1/YVbJ15LOKf4pkat46Eg==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.812.0.tgz", + "integrity": "sha512-/ayAooUZvV1GTomNMrfbhjUHAEaz0Wmio3lKyaTJsW4WdLJXBuzdo57YADRmYYUqx6awzJ6VJ6HGc1Uc6tOlbw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.810.0", + "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/is-array-buffer": "^4.0.0", "@smithy/node-config-provider": "^4.1.1", @@ -930,12 +930,12 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.810.0.tgz", - "integrity": "sha512-CmQHPVopJYDiGQOmqn5AcmhksQ9qNETF0VgU251Q4tsP9s3R9nBR1r2bZwLt5+dCtf9UCa7cBw4jXKHtH38JTg==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.812.0.tgz", + "integrity": "sha512-e8AqRRIaTsunL1hqtO1hksa9oTYdsIbfezHUyVpPGugUIB1lMqPt/DlBsanI85OzUD711UfNSEcZ1mqAxpDOoA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.810.0", + "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/core": "^3.3.3", @@ -969,12 +969,12 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.810.0.tgz", - "integrity": "sha512-gLMJcqgIq7k9skX8u0Yyi+jil4elbsmLf3TuDuqNdlqiZ44/AKdDFfU3mU5tRUtMfP42a3gvb2U3elP0BIeybQ==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.812.0.tgz", + "integrity": "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.810.0", + "@aws-sdk/core": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@smithy/core": "^3.3.3", @@ -987,23 +987,23 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.810.0.tgz", - "integrity": "sha512-w+tGXFSQjzvJ3j2sQ4GJRdD+YXLTgwLd9eG/A+7pjrv2yLLV70M4HqRrFqH06JBjqT5rsOxonc/QSjROyxk+IA==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.812.0.tgz", + "integrity": "sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.810.0", + "@aws-sdk/core": "3.812.0", "@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0", - "@aws-sdk/middleware-user-agent": "3.810.0", + "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-user-agent-browser": "3.804.0", - "@aws-sdk/util-user-agent-node": "3.810.0", + "@aws-sdk/util-user-agent-node": "3.812.0", "@smithy/config-resolver": "^4.1.2", "@smithy/core": "^3.3.3", "@smithy/fetch-http-handler": "^5.0.2", @@ -1053,12 +1053,12 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.810.0.tgz", - "integrity": "sha512-/6TISQT3HUOoY3Gli1EvQVISNGp8XawGKD4igJqwyF3l35VvajzaAfhwk+Tm5TKTCkFh8EFnqWeT+mId5AaUbA==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.812.0.tgz", + "integrity": "sha512-OpyANELjcD2oknkd3/qWanaRaZDx4SSV6NwYuWIk+fuxDZ+KxZZrrfue1X7OAdaP2TdSapbs7xLisxtTuptWYg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.810.0", + "@aws-sdk/signature-v4-multi-region": "3.812.0", "@aws-sdk/types": "3.804.0", "@aws-sdk/util-format-url": "3.804.0", "@smithy/middleware-endpoint": "^4.1.6", @@ -1072,12 +1072,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.810.0.tgz", - "integrity": "sha512-6F6evHRrA0OG/H67YuZBcI7EH4A0O5dIhczo2N0AK9z495uSIv+0xUrSrDhFTZxZjo6gkADIXroRjP1kwqK6ew==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.812.0.tgz", + "integrity": "sha512-JTpk3ZHf7TXYbicKfOKi+VrsBTqcAszg9QR9fQmT9aCxPp39gsF3WsXq7NjepwZ5So11ixGIsPE/jtMym399QQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.810.0", + "@aws-sdk/middleware-sdk-s3": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.1.0", @@ -1089,12 +1089,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.810.0.tgz", - "integrity": "sha512-fdgHRCDpnzsD+0km7zuRbHRysJECfS8o9T9/pZ6XAr1z2FNV/UveHtnUYq0j6XpDMrIm0/suvXbshIjQU+a+sw==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.812.0.tgz", + "integrity": "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "3.810.0", + "@aws-sdk/nested-clients": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", @@ -1185,12 +1185,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.810.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.810.0.tgz", - "integrity": "sha512-T56/ANEGNuvhqVoWZdr+0ZY2hjV93cH2OfGHIlVTVSAMACWG54XehDPESEso1CJNhJGYZPsE+FE42HGCk/XDMg==", + "version": "3.812.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.812.0.tgz", + "integrity": "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.810.0", + "@aws-sdk/middleware-user-agent": "3.812.0", "@aws-sdk/types": "3.804.0", "@smithy/node-config-provider": "^4.1.1", "@smithy/types": "^4.2.0", @@ -1334,9 +1334,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", - "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ "ppc64" ], @@ -1351,9 +1351,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", - "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], @@ -1368,9 +1368,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", - "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], @@ -1385,9 +1385,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", - "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], @@ -1402,9 +1402,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", - "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], @@ -1419,9 +1419,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", - "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], @@ -1436,9 +1436,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", - "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], @@ -1453,9 +1453,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", - "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], @@ -1470,9 +1470,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", - "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], @@ -1487,9 +1487,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", - "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], @@ -1504,9 +1504,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", - "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], @@ -1521,9 +1521,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", - "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], @@ -1538,9 +1538,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", - "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], @@ -1555,9 +1555,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", - "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], @@ -1572,9 +1572,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", - "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], @@ -1589,9 +1589,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", - "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], @@ -1606,9 +1606,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", - "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], @@ -1623,9 +1623,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", - "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", "cpu": [ "arm64" ], @@ -1640,9 +1640,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", - "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], @@ -1657,9 +1657,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", - "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "cpu": [ "arm64" ], @@ -1674,9 +1674,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", - "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], @@ -1691,9 +1691,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", - "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], @@ -1708,9 +1708,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", - "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], @@ -1725,9 +1725,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", - "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], @@ -1742,9 +1742,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", - "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], @@ -1826,9 +1826,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1876,13 +1876,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", - "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -1896,13 +1899,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.14.0", "levn": "^0.4.1" }, "engines": { @@ -2350,252 +2353,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.0.tgz", - "integrity": "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", @@ -2804,9 +2561,9 @@ "license": "BSD-3-Clause" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", - "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", "cpu": [ "arm" ], @@ -2818,9 +2575,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", - "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", "cpu": [ "arm64" ], @@ -2832,9 +2589,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", - "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", "cpu": [ "arm64" ], @@ -2846,9 +2603,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", - "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", "cpu": [ "x64" ], @@ -2860,9 +2617,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", - "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", "cpu": [ "arm64" ], @@ -2874,9 +2631,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", - "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", "cpu": [ "x64" ], @@ -2888,9 +2645,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", - "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", "cpu": [ "arm" ], @@ -2902,9 +2659,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", - "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", "cpu": [ "arm" ], @@ -2916,9 +2673,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", - "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", "cpu": [ "arm64" ], @@ -2930,9 +2687,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", - "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", + "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", "cpu": [ "arm64" ], @@ -2944,9 +2701,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", - "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", "cpu": [ "loong64" ], @@ -2958,9 +2715,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", - "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", "cpu": [ "ppc64" ], @@ -2972,9 +2729,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", - "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", "cpu": [ "riscv64" ], @@ -2986,9 +2743,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", - "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", "cpu": [ "riscv64" ], @@ -3000,9 +2757,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", - "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", "cpu": [ "s390x" ], @@ -3014,9 +2771,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", - "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", + "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", "cpu": [ "x64" ], @@ -3028,9 +2785,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", - "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", + "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", "cpu": [ "x64" ], @@ -3042,9 +2799,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", - "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", "cpu": [ "arm64" ], @@ -3056,9 +2813,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", - "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", "cpu": [ "ia32" ], @@ -3070,9 +2827,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", - "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", "cpu": [ "x64" ], @@ -3479,12 +3236,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.1.tgz", + "integrity": "sha512-Vsay2mzq05DwNi9jK01yCFtfvu9HimmgC7a4HTs7lhX12Sx8aWsH0mfz6q/02yspSp+lOB+Q2HJwi4IV2GKz7A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.0", "tslib": "^2.6.2" }, "engines": { @@ -3544,16 +3301,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.0.tgz", - "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.1.tgz", + "integrity": "sha512-zy8Repr5zvT0ja+Tf5wjV/Ba6vRrhdiDcp/ww6cvqYbSEudIkziDe3uppNRlFoCViyJXdPnLcwyZdDLA4CHzSg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", + "@smithy/util-middleware": "^4.0.3", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -3581,9 +3338,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.0.tgz", + "integrity": "sha512-+1iaIQHthDh9yaLhRzaoQxRk+l9xlk+JjMFxGRhNLz+m9vKOkjNeU8QuB4w3xvzHyVR/BVlp/4AXDHjoRIkfgQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3730,12 +3487,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.3.tgz", + "integrity": "sha512-iIsC6qZXxkD7V3BzTw3b1uK8RVC1M8WvwNxK1PKrH9FnxntCd30CSunXjL/8iJBE8Z0J14r2P69njwIpRG4FBQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.3.0", "tslib": "^2.6.2" }, "engines": { @@ -4101,14 +3858,14 @@ "license": "MIT" }, "node_modules/@vitest/expect": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz", - "integrity": "sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz", + "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -4117,13 +3874,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.3.tgz", - "integrity": "sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz", + "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.3", + "@vitest/spy": "3.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -4144,9 +3901,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", - "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", + "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", "dev": true, "license": "MIT", "dependencies": { @@ -4157,13 +3914,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.3.tgz", - "integrity": "sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz", + "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.1.3", + "@vitest/utils": "3.1.4", "pathe": "^2.0.3" }, "funding": { @@ -4171,13 +3928,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.3.tgz", - "integrity": "sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz", + "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.3", + "@vitest/pretty-format": "3.1.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -4186,9 +3943,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz", - "integrity": "sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz", + "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -4199,13 +3956,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.3.tgz", - "integrity": "sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.3", + "@vitest/pretty-format": "3.1.4", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -4814,81 +4571,6 @@ "node": "*" } }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/body-parser/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/body-parser/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -4952,9 +4634,9 @@ } }, "node_modules/bullmq": { - "version": "5.52.2", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.52.2.tgz", - "integrity": "sha512-fK/dKIv8ymyys4K+zeNEPA+yuYWzRPmBWUmwIMz8DvYekadl8VG19yUx94Na0n0cLAi+spdn3a/+ufkYK7CBUg==", + "version": "5.52.3", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.52.3.tgz", + "integrity": "sha512-UaVkg+uSgylYWjD6/d8TVm87SjDVZ5jKwDVh/pJACmStn71aIzOIpGazh2JrkGISgT10Q/lG2I40FhPg0KgNCQ==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", @@ -6484,9 +6166,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", - "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6497,31 +6179,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.3", - "@esbuild/android-arm": "0.25.3", - "@esbuild/android-arm64": "0.25.3", - "@esbuild/android-x64": "0.25.3", - "@esbuild/darwin-arm64": "0.25.3", - "@esbuild/darwin-x64": "0.25.3", - "@esbuild/freebsd-arm64": "0.25.3", - "@esbuild/freebsd-x64": "0.25.3", - "@esbuild/linux-arm": "0.25.3", - "@esbuild/linux-arm64": "0.25.3", - "@esbuild/linux-ia32": "0.25.3", - "@esbuild/linux-loong64": "0.25.3", - "@esbuild/linux-mips64el": "0.25.3", - "@esbuild/linux-ppc64": "0.25.3", - "@esbuild/linux-riscv64": "0.25.3", - "@esbuild/linux-s390x": "0.25.3", - "@esbuild/linux-x64": "0.25.3", - "@esbuild/netbsd-arm64": "0.25.3", - "@esbuild/netbsd-x64": "0.25.3", - "@esbuild/openbsd-arm64": "0.25.3", - "@esbuild/openbsd-x64": "0.25.3", - "@esbuild/sunos-x64": "0.25.3", - "@esbuild/win32-arm64": "0.25.3", - "@esbuild/win32-ia32": "0.25.3", - "@esbuild/win32-x64": "0.25.3" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/escalade": { @@ -6566,9 +6248,9 @@ } }, "node_modules/eslint": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", - "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6576,14 +6258,13 @@ "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.26.0", - "@eslint/plugin-kit": "^0.2.8", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", - "@modelcontextprotocol/sdk": "^1.8.0", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -6607,8 +6288,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "zod": "^3.24.2" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -6815,29 +6495,6 @@ "node": ">=0.8.x" } }, - "node_modules/eventsource": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", - "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", - "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/expect-type": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", @@ -6894,22 +6551,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" - } - }, "node_modules/express/node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -8418,13 +8059,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -10095,16 +9729,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -10359,22 +9983,6 @@ "node": ">= 0.6" } }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -10711,9 +10319,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", - "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", + "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", "dev": true, "license": "MIT", "dependencies": { @@ -10727,56 +10335,29 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.1", - "@rollup/rollup-android-arm64": "4.40.1", - "@rollup/rollup-darwin-arm64": "4.40.1", - "@rollup/rollup-darwin-x64": "4.40.1", - "@rollup/rollup-freebsd-arm64": "4.40.1", - "@rollup/rollup-freebsd-x64": "4.40.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", - "@rollup/rollup-linux-arm-musleabihf": "4.40.1", - "@rollup/rollup-linux-arm64-gnu": "4.40.1", - "@rollup/rollup-linux-arm64-musl": "4.40.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-musl": "4.40.1", - "@rollup/rollup-linux-s390x-gnu": "4.40.1", - "@rollup/rollup-linux-x64-gnu": "4.40.1", - "@rollup/rollup-linux-x64-musl": "4.40.1", - "@rollup/rollup-win32-arm64-msvc": "4.40.1", - "@rollup/rollup-win32-ia32-msvc": "4.40.1", - "@rollup/rollup-win32-x64-msvc": "4.40.1", + "@rollup/rollup-android-arm-eabi": "4.41.0", + "@rollup/rollup-android-arm64": "4.41.0", + "@rollup/rollup-darwin-arm64": "4.41.0", + "@rollup/rollup-darwin-x64": "4.41.0", + "@rollup/rollup-freebsd-arm64": "4.41.0", + "@rollup/rollup-freebsd-x64": "4.41.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", + "@rollup/rollup-linux-arm-musleabihf": "4.41.0", + "@rollup/rollup-linux-arm64-gnu": "4.41.0", + "@rollup/rollup-linux-arm64-musl": "4.41.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-musl": "4.41.0", + "@rollup/rollup-linux-s390x-gnu": "4.41.0", + "@rollup/rollup-linux-x64-gnu": "4.41.0", + "@rollup/rollup-linux-x64-musl": "4.41.0", + "@rollup/rollup-win32-arm64-msvc": "4.41.0", + "@rollup/rollup-win32-ia32-msvc": "4.41.0", + "@rollup/rollup-win32-x64-msvc": "4.41.0", "fsevents": "~2.3.2" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/rsa-pem-from-mod-exp": { "version": "0.8.6", "resolved": "https://registry.npmjs.org/rsa-pem-from-mod-exp/-/rsa-pem-from-mod-exp-0.8.6.tgz", @@ -12572,9 +12153,9 @@ } }, "node_modules/vite-node": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.3.tgz", - "integrity": "sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz", + "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==", "dev": true, "license": "MIT", "dependencies": { @@ -12595,19 +12176,19 @@ } }, "node_modules/vitest": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz", - "integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz", + "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.1.3", - "@vitest/mocker": "3.1.3", - "@vitest/pretty-format": "^3.1.3", - "@vitest/runner": "3.1.3", - "@vitest/snapshot": "3.1.3", - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", + "@vitest/expect": "3.1.4", + "@vitest/mocker": "3.1.4", + "@vitest/pretty-format": "^3.1.4", + "@vitest/runner": "3.1.4", + "@vitest/snapshot": "3.1.4", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.2.1", @@ -12620,7 +12201,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.3", + "vite-node": "3.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -12636,8 +12217,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.3", - "@vitest/ui": "3.1.3", + "@vitest/browser": "3.1.4", + "@vitest/ui": "3.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -13454,26 +13035,6 @@ "engines": { "node": ">= 14" } - }, - "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } } } } diff --git a/package.json b/package.json index 10afa03ac..a2da483f7 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,14 @@ "job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js" }, "dependencies": { - "@aws-sdk/client-cloudwatch-logs": "^3.810.0", - "@aws-sdk/client-elasticache": "^3.810.0", - "@aws-sdk/client-s3": "^3.810.0", - "@aws-sdk/client-secrets-manager": "^3.810.0", - "@aws-sdk/client-ses": "^3.810.0", - "@aws-sdk/credential-provider-node": "^3.810.0", - "@aws-sdk/lib-storage": "^3.810.0", - "@aws-sdk/s3-request-presigner": "^3.810.0", + "@aws-sdk/client-cloudwatch-logs": "^3.812.0", + "@aws-sdk/client-elasticache": "^3.812.0", + "@aws-sdk/client-s3": "^3.812.0", + "@aws-sdk/client-secrets-manager": "^3.812.0", + "@aws-sdk/client-ses": "^3.812.0", + "@aws-sdk/credential-provider-node": "^3.812.0", + "@aws-sdk/lib-storage": "^3.812.0", + "@aws-sdk/s3-request-presigner": "^3.812.0", "@opensearch-project/opensearch": "^2.13.0", "@socket.io/admin-ui": "^0.5.1", "@socket.io/redis-adapter": "^8.3.0", @@ -31,7 +31,7 @@ "aws4": "^1.13.2", "axios": "^1.8.4", "better-queue": "^3.8.12", - "bullmq": "^5.52.2", + "bullmq": "^5.52.3", "chart.js": "^4.4.8", "cloudinary": "^2.6.1", "compression": "^1.8.0", @@ -73,14 +73,14 @@ "xmlbuilder2": "^3.1.1" }, "devDependencies": { - "@eslint/js": "^9.26.0", - "eslint": "^9.26.0", + "@eslint/js": "^9.27.0", + "eslint": "^9.27.0", "eslint-plugin-react": "^7.37.5", "globals": "^15.15.0", "mock-require": "^3.0.3", "p-limit": "^3.1.0", "prettier": "^3.5.3", "supertest": "^7.1.1", - "vitest": "^3.1.3" + "vitest": "^3.1.4" } } From 985d0669788e772788592b8141d005fc5685e411 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 May 2025 12:49:32 -0400 Subject: [PATCH 10/22] feature/IO-3182-Phone-Number-Consent - Finish Database changes --- hasura/metadata/tables.yaml | 37 +++++++++++++++++++ .../down.sql | 1 + .../up.sql | 2 + 3 files changed, 40 insertions(+) create mode 100644 hasura/migrations/1747759446622_create_table_public_phone_number_consent_history/down.sql create mode 100644 hasura/migrations/1747759446622_create_table_public_phone_number_consent_history/up.sql diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 1b4faac05..17c162e07 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -5870,6 +5870,14 @@ - name: bodyshop using: foreign_key_constraint_on: bodyshopid + array_relationships: + - name: phone_number_consent_histories + using: + foreign_key_constraint_on: + column: phone_number_consent_id + table: + name: phone_number_consent_history + schema: public insert_permissions: - role: user permission: @@ -5925,6 +5933,35 @@ filter: {} check: null comment: "" +- table: + name: phone_number_consent_history + schema: public + object_relationships: + - name: phone_number_consent + using: + foreign_key_constraint_on: phone_number_consent_id + select_permissions: + - role: user + permission: + columns: + - new_value + - old_value + - changed_by + - reason + - changed_at + - id + - phone_number_consent_id + filter: + phone_number_consent: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + comment: "" - table: name: phonebook schema: public diff --git a/hasura/migrations/1747759446622_create_table_public_phone_number_consent_history/down.sql b/hasura/migrations/1747759446622_create_table_public_phone_number_consent_history/down.sql new file mode 100644 index 000000000..311862382 --- /dev/null +++ b/hasura/migrations/1747759446622_create_table_public_phone_number_consent_history/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."phone_number_consent_history"; diff --git a/hasura/migrations/1747759446622_create_table_public_phone_number_consent_history/up.sql b/hasura/migrations/1747759446622_create_table_public_phone_number_consent_history/up.sql new file mode 100644 index 000000000..10a07721f --- /dev/null +++ b/hasura/migrations/1747759446622_create_table_public_phone_number_consent_history/up.sql @@ -0,0 +1,2 @@ +CREATE TABLE "public"."phone_number_consent_history" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "phone_number_consent_id" uuid NOT NULL, "old_value" boolean NOT NULL, "new_value" boolean NOT NULL, "reason" text NOT NULL, "changed_at" timestamptz NOT NULL DEFAULT now(), "changed_by" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("phone_number_consent_id") REFERENCES "public"."phone_number_consent"("id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id")); +CREATE EXTENSION IF NOT EXISTS pgcrypto; From 9d81c68a4d11e418d76513b4cd26d27a4854ebcb Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 May 2025 13:14:05 -0400 Subject: [PATCH 11/22] feature/IO-3182-Phone-Number-Consent - Checkpoint --- server/sms/receive.js | 96 ++++++++++++++++++++++++--------------- server/sms/send.js | 24 ++++++---- server/sms/status.js | 32 +++++++++---- server/utils/ioHelpers.js | 8 ++++ 4 files changed, 105 insertions(+), 55 deletions(-) diff --git a/server/sms/receive.js b/server/sms/receive.js index f08cf727e..f880105e9 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -1,16 +1,61 @@ -const path = require("path"); -require("dotenv").config({ - path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) -}); - const client = require("../graphql-client/graphql-client").client; -const queries = require("../graphql-client/queries"); +const { + FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, + UNARCHIVE_CONVERSATION, + CREATE_CONVERSATION, + INSERT_MESSAGE +} = require("../graphql-client/queries"); const { phone } = require("phone"); const { admin } = require("../firebase/firebase-handler"); const logger = require("../utils/logger"); const InstanceManager = require("../utils/instanceMgr").default; -exports.receive = async (req, res) => { +/** + * Generate an array of media URLs from the request body + * @param body + * @returns {null|*[]} + */ +const generateMediaArray = (body) => { + const { NumMedia } = body; + if (parseInt(NumMedia) > 0) { + const ret = []; + for (let i = 0; i < parseInt(NumMedia); i++) { + ret.push(body[`MediaUrl${i}`]); + } + return ret; + } else { + return null; + } +}; + +/** + * Handle errors during the message receiving process + * @param req + * @param error + * @param res + * @param context + */ +const handleError = (req, error, res, context) => { + logger.log("sms-inbound-error", "ERROR", "api", null, { + msid: req.body.SmsMessageSid, + text: req.body.Body, + image: !!req.body.MediaUrl0, + image_path: generateMediaArray(req.body), + messagingServiceSid: req.body.MessagingServiceSid, + context, + error + }); + + res.status(500).json({ error: error.message || "Internal Server Error" }); +}; + +/** + * Receive an inbound SMS message + * @param req + * @param res + * @returns {Promise<*>} + */ +const receive = async (req, res) => { const { ioRedis, ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } @@ -35,7 +80,7 @@ exports.receive = async (req, res) => { try { // Step 1: Find the bodyshop and existing conversation - const response = await client.request(queries.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, { + const response = await client.request(FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, { mssid: req.body.MessagingServiceSid, phone: phone(req.body.From).phoneNumber }); @@ -68,14 +113,14 @@ exports.receive = async (req, res) => { // Unarchive the conversation if necessary if (existingConversation.archived) { - await client.request(queries.UNARCHIVE_CONVERSATION, { + await client.request(UNARCHIVE_CONVERSATION, { id: conversationid, archived: false }); } } else { // Create a new conversation - const newConversationResponse = await client.request(queries.CREATE_CONVERSATION, { + const newConversationResponse = await client.request(CREATE_CONVERSATION, { conversation: { bodyshopid: bodyshop.id, phone_num: phone(req.body.From).phoneNumber, @@ -90,7 +135,7 @@ exports.receive = async (req, res) => { newMessage.conversationid = conversationid; // Step 3: Insert the message into the conversation - const insertresp = await client.request(queries.INSERT_MESSAGE, { + const insertresp = await client.request(INSERT_MESSAGE, { msg: newMessage, conversationid: conversationid }); @@ -137,7 +182,7 @@ exports.receive = async (req, res) => { notification: { title: InstanceManager({ imex: `ImEX Online Message - ${message.conversation.phone_num}`, - rome: `Rome Online Message - ${message.conversation.phone_num}`, + rome: `Rome Online Message - ${message.conversation.phone_num}` }), body: message.image_path ? `Image ${message.text}` : message.text }, @@ -161,29 +206,6 @@ exports.receive = async (req, res) => { } }; -const generateMediaArray = (body) => { - const { NumMedia } = body; - if (parseInt(NumMedia) > 0) { - const ret = []; - for (let i = 0; i < parseInt(NumMedia); i++) { - ret.push(body[`MediaUrl${i}`]); - } - return ret; - } else { - return null; - } -}; - -const handleError = (req, error, res, context) => { - logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - messagingServiceSid: req.body.MessagingServiceSid, - context, - error - }); - - res.status(500).json({ error: error.message || "Internal Server Error" }); +module.exports = { + receive }; diff --git a/server/sms/send.js b/server/sms/send.js index eb8cb6e5f..bc0a95da9 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -1,16 +1,17 @@ -const path = require("path"); -require("dotenv").config({ - path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) -}); - const twilio = require("twilio"); const { phone } = require("phone"); -const queries = require("../graphql-client/queries"); +const { INSERT_MESSAGE } = require("../graphql-client/queries"); const logger = require("../utils/logger"); const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); const gqlClient = require("../graphql-client/graphql-client").client; -exports.send = async (req, res) => { +/** + * Send an outbound SMS message + * @param req + * @param res + * @returns {Promise} + */ +const send = async (req, res) => { const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body; const { ioRedis, @@ -64,7 +65,10 @@ exports.send = async (req, res) => { }; try { - const gqlResponse = await gqlClient.request(queries.INSERT_MESSAGE, { msg: newMessage, conversationid }); + const gqlResponse = await gqlClient.request(INSERT_MESSAGE, { + msg: newMessage, + conversationid + }); logger.log("sms-outbound-success", "DEBUG", req.user.email, null, { msid: message.sid, @@ -111,3 +115,7 @@ exports.send = async (req, res) => { res.status(500).json({ success: false, message: "Failed to send message through Twilio." }); } }; + +module.exports = { + send +}; diff --git a/server/sms/status.js b/server/sms/status.js index 0e29bbf8f..509c76d6b 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -1,13 +1,14 @@ -const path = require("path"); -require("dotenv").config({ - path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) -}); - const client = require("../graphql-client/graphql-client").client; -const queries = require("../graphql-client/queries"); +const { UPDATE_MESSAGE_STATUS, MARK_MESSAGES_AS_READ } = require("../graphql-client/queries"); const logger = require("../utils/logger"); -exports.status = async (req, res) => { +/** + * Handle the status of an SMS message + * @param req + * @param res + * @returns {Promise<*>} + */ +const status = async (req, res) => { const { SmsSid, SmsStatus } = req.body; const { ioRedis, @@ -21,7 +22,7 @@ exports.status = async (req, res) => { } // Update message status in the database - const response = await client.request(queries.UPDATE_MESSAGE_STATUS, { + const response = await client.request(UPDATE_MESSAGE_STATUS, { msid: SmsSid, fields: { status: SmsStatus } }); @@ -65,7 +66,13 @@ exports.status = async (req, res) => { } }; -exports.markConversationRead = async (req, res) => { +/** + * Mark a conversation as read + * @param req + * @param res + * @returns {Promise<*>} + */ +const markConversationRead = async (req, res) => { const { ioRedis, ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } @@ -80,7 +87,7 @@ exports.markConversationRead = async (req, res) => { } try { - const response = await client.request(queries.MARK_MESSAGES_AS_READ, { + const response = await client.request(MARK_MESSAGES_AS_READ, { conversationId }); @@ -104,3 +111,8 @@ exports.markConversationRead = async (req, res) => { res.status(500).json({ error: "Failed to mark conversation as read." }); } }; + +module.exports = { + status, + markConversationRead +}; diff --git a/server/utils/ioHelpers.js b/server/utils/ioHelpers.js index 584d45ce7..f26440a50 100644 --- a/server/utils/ioHelpers.js +++ b/server/utils/ioHelpers.js @@ -1,3 +1,11 @@ +/** + * @module ioHelpers + * @param app + * @param api + * @param io + * @param logger + * @returns {{getBodyshopRoom: (function(*): string), getBodyshopConversationRoom: (function({bodyshopId: *, conversationId: *}): string)}} + */ const applyIOHelpers = ({ app, api, io, logger }) => { // Global Bodyshop Room const getBodyshopRoom = (bodyshopId) => `bodyshop-broadcast-room:${bodyshopId}`; From 83860152a9faedc32fc84b425dd0b4bf6a014418 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 May 2025 16:04:36 -0400 Subject: [PATCH 12/22] feature/IO-3182-Phone-Number-Consent - Checkpoint --- .../chat-affix/chat-affix.container.jsx | 56 +++++++- .../chat-conversation-list.component.jsx | 51 +++++-- .../chat-conversation-list.styles.scss | 2 +- .../chat-media-selector.component.jsx | 8 +- .../chat-send-message.component.jsx | 27 +++- .../phone-number-consent.component.jsx | 131 ++++++++++++++++++ .../shop-info/shop-info.consent.component.jsx | 51 +++++++ client/src/graphql/bodyshop.queries.js | 10 ++ client/src/graphql/consent.queries.js | 90 ++++++++++++ client/src/pages/shop/shop.page.component.jsx | 10 +- server/graphql-client/queries.js | 101 ++++++++++++++ server/sms/receive.js | 46 ++++-- 12 files changed, 540 insertions(+), 43 deletions(-) create mode 100644 client/src/components/phone-number-consent/phone-number-consent.component.jsx create mode 100644 client/src/components/shop-info/shop-info.consent.component.jsx create mode 100644 client/src/graphql/consent.queries.js diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx index 9885b8551..cf8afce3b 100644 --- a/client/src/components/chat-affix/chat-affix.container.jsx +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -8,6 +8,7 @@ import ChatPopupComponent from "../chat-popup/chat-popup.component"; import "./chat-affix.styles.scss"; import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers"; import { useSocket } from "../../contexts/SocketIO/useSocket.js"; +import { GET_PHONE_NUMBER_CONSENT } from "../../graphql/consent.queries.js"; export function ChatAffixContainer({ bodyshop, chatVisible }) { const { t } = useTranslation(); @@ -34,16 +35,59 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) { SubscribeToTopicForFCMNotification(); - //Register WS handlers + // Register WebSocket handlers if (socket && socket.connected) { registerMessagingHandlers({ socket, client }); - } - return () => { - if (socket && socket.connected) { + // Handle consent-changed events + const handleConsentChanged = ({ bodyshopId, phone_number, consent_status }) => { + try { + client.cache.writeQuery( + { + query: GET_PHONE_NUMBER_CONSENT, + variables: { bodyshopid: bodyshopId, phone_number } + }, + (data) => { + if (!data?.phone_number_consent?.[0]) { + return { + phone_number_consent: [ + { + __typename: "phone_number_consent", + id: null, + bodyshopid: bodyshopId, + phone_number, + consent_status, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + consent_updated_at: new Date().toISOString(), + history: [] + } + ] + }; + } + return { + phone_number_consent: [ + { + ...data.phone_number_consent[0], + consent_status, + consent_updated_at: new Date().toISOString() + } + ] + }; + } + ); + } catch (error) { + console.error("Error updating consent cache:", error); + } + }; + + socket.on("consent-changed", handleConsentChanged); + + return () => { + socket.off("consent-changed", handleConsentChanged); unregisterMessagingHandlers({ socket }); - } - }; + }; + } }, [bodyshop, socket, t, client]); if (!bodyshop || !bodyshop.messagingservicesid) return <>; diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index 16d4c0bf1..ede2a2570 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -10,6 +10,10 @@ import PhoneFormatter from "../../utils/PhoneFormatter"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import _ from "lodash"; import "./chat-conversation-list.styles.scss"; +import { useQuery } from "@apollo/client"; +import { GET_PHONE_NUMBER_CONSENTS } from "../../graphql/consent.queries.js"; +import { phone } from "phone"; +import { useTranslation } from "react-i18next"; const mapStateToProps = createStructuredSelector({ selectedConversation: selectSelectedConversation @@ -20,25 +24,45 @@ const mapDispatchToProps = (dispatch) => ({ }); function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) { - // That comma is there for a reason, do not remove it + const { t } = useTranslation(); const [, forceUpdate] = useState(false); - // Re-render every minute + // Normalize phone numbers and fetch consent statuses + const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "")); + const { data: consentData, loading: consentLoading } = useQuery(GET_PHONE_NUMBER_CONSENTS, { + variables: { + bodyshopid: conversationList[0]?.bodyshopid, + phone_numbers: phoneNumbers + }, + skip: !conversationList.length || !conversationList[0]?.bodyshopid, + fetchPolicy: "cache-and-network" + }); + + // Create a map of phone number to consent status + const consentMap = React.useMemo(() => { + const map = new Map(); + consentData?.phone_number_consent?.forEach((consent) => { + map.set(consent.phone_number, consent.consent_status); + }); + return map; + }, [consentData]); + useEffect(() => { const interval = setInterval(() => { - forceUpdate((prev) => !prev); // Toggle state to trigger re-render - }, 60000); // 1 minute in milliseconds - - return () => clearInterval(interval); // Cleanup on unmount + forceUpdate((prev) => !prev); + }, 60000); + return () => clearInterval(interval); }, []); - // Memoize the sorted conversation list const sortedConversationList = React.useMemo(() => { return _.orderBy(conversationList, ["updated_at"], ["desc"]); }, [conversationList]); - const renderConversation = (index) => { + const renderConversation = (index, t) => { const item = sortedConversationList[index]; + const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); + const isConsented = consentMap.get(normalizedPhone) ?? false; + const cardContentRight = {item.updated_at}; const cardContentLeft = item.job_conversations.length > 0 @@ -60,7 +84,12 @@ function ChatConversationListComponent({ conversationList, selectedConversation, ); - const cardExtra = ; + const cardExtra = ( + <> + + {!isConsented && {t("messaging.labels.no_consent")}} + + ); const getCardStyle = () => item.id === selectedConversation @@ -73,7 +102,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation, onClick={() => setSelectedConversation(item.id)} className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`} > - +
{cardContentLeft}
{cardContentRight}
@@ -85,7 +114,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
renderConversation(index)} + itemContent={(index) => renderConversation(index, t)} style={{ height: "100%", width: "100%" }} />
diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss index e6169777c..86cf06152 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss +++ b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss @@ -24,7 +24,7 @@ /* Add spacing and better alignment for items */ .chat-list-item { - padding: 0.5rem 0; /* Add spacing between list items */ + padding: 0.2rem 0; /* Add spacing between list items */ .ant-card { border-radius: 8px; /* Slight rounding for card edges */ 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 b7e7d64a3..162789fb6 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 @@ -37,7 +37,7 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c fetchPolicy: "network-only", nextFetchPolicy: "network-only", variables: { - jobId: conversation.job_conversations[0] && conversation.job_conversations[0].jobid + jobId: conversation.job_conversations[0] && conversation.job_conversations[0]?.jobid }, skip: !open || !conversation.job_conversations || conversation.job_conversations.length === 0 @@ -67,14 +67,14 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c <> {!bodyshop.uselocalmediaserver && ( )} {bodyshop.uselocalmediaserver && open && ( )} @@ -89,7 +89,7 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c {bodyshop.uselocalmediaserver && open && ( )} diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index 824d5e591..29e5bb8c6 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -10,6 +10,10 @@ import { selectIsSending, selectMessage } from "../../redux/messaging/messaging. import { selectBodyshop } from "../../redux/user/user.selectors"; import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component"; import ChatPresetsComponent from "../chat-presets/chat-presets.component"; +import { useQuery } from "@apollo/client"; +import { GET_PHONE_NUMBER_CONSENT } from "../../graphql/consent.queries"; +import AlertComponent from "../alert/alert.component"; +import { phone } from "phone"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -25,16 +29,23 @@ const mapDispatchToProps = (dispatch) => ({ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) { const inputArea = useRef(null); const [selectedMedia, setSelectedMedia] = useState([]); + const { t } = useTranslation(); + + const normalizedPhone = phone(conversation.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); + const { data: consentData } = useQuery(GET_PHONE_NUMBER_CONSENT, { + variables: { bodyshopid: bodyshop.id, phone_number: normalizedPhone }, + fetchPolicy: "cache-and-network" + }); + const isConsented = consentData?.phone_number_consent?.[0]?.consent_status ?? false; useEffect(() => { inputArea.current.focus(); }, [isSending, setMessage]); - const { t } = useTranslation(); - const handleEnter = () => { const selectedImages = selectedMedia.filter((i) => i.isSelected); if ((message === "" || !message) && selectedImages.length === 0) return; + if (!isConsented) return; logImEXEvent("messaging_send_message"); if (selectedImages.length < 11) { @@ -44,7 +55,8 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi messagingServiceSid: bodyshop.messagingservicesid, conversationid: conversation.id, selectedMedia: selectedImages, - imexshopid: bodyshop.imexshopid + imexshopid: bodyshop.imexshopid, + bodyshopid: bodyshop.id }; sendMessage(newMessage); setSelectedMedia( @@ -57,6 +69,9 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi return (
+ {!isConsented && ( + + )} setMessage(e.target.value)} onPressEnter={(event) => { event.preventDefault(); - if (!!!event.shiftKey) handleEnter(); + if (!event.shiftKey && isConsented) handleEnter(); }} /> ({}); + +function PhoneNumberConsentList({ bodyshop }) { + const { t } = useTranslation(); + const [search, setSearch] = useState(""); + const { loading, data } = useQuery(GET_PHONE_NUMBER_CONSENTS, { + variables: { bodyshopid: bodyshop.id, search }, + fetchPolicy: "network-only" + }); + const [setConsent] = useMutation(SET_PHONE_NUMBER_CONSENT); + const [bulkSetConsent] = useMutation(BULK_SET_PHONE_NUMBER_CONSENT); + + const columns = [ + { + title: t("consent.phone_number"), + dataIndex: "phone_number", + render: (text) => {text} + }, + { + title: t("consent.status"), + dataIndex: "consent_status", + render: (status, record) => ( + + + setConsent({ + variables: { + bodyshopid: bodyshop.id, + phone_number: record.phone_number, + consent_status: checked, + reason: "Manual override in app", + changed_by: "user" // Replace with actual user email from context + }, + optimisticResponse: { + insert_phone_number_consent_one: { + __typename: "phone_number_consent", + id: record.id, + bodyshopid: bodyshop.id, + phone_number: record.phone_number, + consent_status: checked, + created_at: record.created_at, + updated_at: new Date().toISOString(), + consent_updated_at: new Date().toISOString() + } + } + }) + } + /> + + ) + }, + { + title: t("consent.updated_at"), + dataIndex: "consent_updated_at", + render: (text) => {text} + } + ]; + + const handleBulkUpload = async (file) => { + const reader = new FileReader(); + reader.onload = async (e) => { + const text = e.target.result; + const lines = text.split("\n").slice(1); // Skip header + const objects = lines + .filter((line) => line.trim()) + .map((line) => { + const [phone_number, consent_status] = line.split(","); + return { + bodyshopid: bodyshop.id, + phone_number: phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""), + consent_status: consent_status.trim().toLowerCase() === "true" + }; + }); + + try { + await bulkSetConsent({ + variables: { objects }, + context: { headers: { "x-reason": "System update via bulk upload", "x-changed-by": "system" } } + }); + } catch (error) { + console.error("Bulk upload failed:", error); + } + }; + reader.readAsText(file); + return false; + }; + + return ( +
+ setSearch(value)} + style={{ marginBottom: 16 }} + /> + + + + + + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(PhoneNumberConsentList); diff --git a/client/src/components/shop-info/shop-info.consent.component.jsx b/client/src/components/shop-info/shop-info.consent.component.jsx new file mode 100644 index 000000000..74975e02d --- /dev/null +++ b/client/src/components/shop-info/shop-info.consent.component.jsx @@ -0,0 +1,51 @@ +import { useMutation } from "@apollo/client"; +import { Switch, Typography } from "antd"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { UPDATE_BODYSHOP_ENFORCE_CONSENT } from "../../graphql/bodyshop.queries"; +import PhoneNumberConsentList from "../phone-number-consent/phone-number-consent.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +function ShopInfoConsentComponent({ bodyshop }) { + const { t } = useTranslation(); + + const [updateEnforceConsent] = useMutation(UPDATE_BODYSHOP_ENFORCE_CONSENT); + + console.dir(bodyshop); + + const enforceConsent = bodyshop?.enforce_sms_consent ?? false; + + return ( +
+ {t("settings.title")} +
+ {t("settings.enforce_sms_consent")} + + updateEnforceConsent({ + variables: { id: bodyshop.id, enforce_sms_consent: checked }, + optimisticResponse: { + update_bodyshops_by_pk: { + __typename: "bodyshops", + id: bodyshop.id, + enforce_sms_consent: checked + } + } + }) + } + /> +
+ +
+ ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoConsentComponent); diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index 7faff13a2..af16899cf 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -142,6 +142,7 @@ export const QUERY_BODYSHOP = gql` intellipay_config md_ro_guard notification_followers + enforce_sms_consent employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) { id name @@ -363,3 +364,12 @@ export const GET_ACTIVE_EMPLOYEES_IN_SHOP = gql` } } `; + +export const UPDATE_BODYSHOP_ENFORCE_CONSENT = gql` + mutation UPDATE_BODYSHOP_ENFORCE_CONSENT($id: uuid!, $enforce_sms_consent: Boolean!) { + update_bodyshops_by_pk(pk_columns: { id: $id }, _set: { enforce_sms_consent: $enforce_sms_consent }) { + id + enforce_sms_consent + } + } +`; diff --git a/client/src/graphql/consent.queries.js b/client/src/graphql/consent.queries.js new file mode 100644 index 000000000..8a3f78c8f --- /dev/null +++ b/client/src/graphql/consent.queries.js @@ -0,0 +1,90 @@ +import { gql } from "@apollo/client"; + +export const GET_PHONE_NUMBER_CONSENT = gql` + query GET_PHONE_NUMBER_CONSENT($bodyshopid: uuid!, $phone_number: String!) { + phone_number_consent(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) { + id + bodyshopid + phone_number + consent_status + created_at + updated_at + consent_updated_at + history(order_by: { changed_at: desc }, limit: 1) { + reason + } + } + } +`; + +export const GET_PHONE_NUMBER_CONSENTS = gql` + query GET_PHONE_NUMBER_CONSENTS($bodyshopid: uuid!, $phone_numbers: [String!]) { + phone_number_consent( + where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _in: $phone_numbers } } + order_by: { consent_updated_at: desc } + ) { + id + bodyshopid + phone_number + consent_status + created_at + updated_at + consent_updated_at + history(order_by: { changed_at: desc }, limit: 1) { + reason + } + } + } +`; + +export const SET_PHONE_NUMBER_CONSENT = gql` + mutation SET_PHONE_NUMBER_CONSENT( + $bodyshopid: uuid! + $phone_number: String! + $consent_status: Boolean! + $reason: String! + $changed_by: String! + ) { + insert_phone_number_consent_one( + object: { + bodyshopid: $bodyshopid + phone_number: $phone_number + consent_status: $consent_status + consent_updated_at: "now()" + } + on_conflict: { + constraint: phone_number_consent_bodyshopid_phone_number_key + update_columns: [consent_status, consent_updated_at] + } + ) { + id + bodyshopid + phone_number + consent_status + created_at + updated_at + consent_updated_at + } + } +`; + +export const BULK_SET_PHONE_NUMBER_CONSENT = gql` + mutation BULK_SET_PHONE_NUMBER_CONSENT($objects: [phone_number_consent_insert_input!]!) { + insert_phone_number_consent( + objects: $objects + on_conflict: { + constraint: phone_number_consent_bodyshopid_phone_number_key + update_columns: [consent_status, consent_updated_at] + } + ) { + affected_rows + returning { + id + bodyshopid + phone_number + consent_status + consent_updated_at + } + } + } +`; diff --git a/client/src/pages/shop/shop.page.component.jsx b/client/src/pages/shop/shop.page.component.jsx index b4b354b1d..b6ded16f6 100644 --- a/client/src/pages/shop/shop.page.component.jsx +++ b/client/src/pages/shop/shop.page.component.jsx @@ -10,10 +10,10 @@ import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.comp import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container"; import ShopInfoContainer from "../../components/shop-info/shop-info.container"; import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component"; +import ShopInfoConsentComponent from "../../components/shop-info/shop-info.consent.component"; import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; - import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component"; import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container"; @@ -91,6 +91,14 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) { children: }); } + + // Add Consent Settings tab + items.push({ + key: "consent", + label: t("bodyshop.labels.consent_settings"), + children: + }); + return ( history({ search: `?tab=${key}` })} items={items} /> diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index d61d814b9..4ed9e5905 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2805,6 +2805,7 @@ exports.GET_BODYSHOP_BY_ID = ` intellipay_config state notification_followers + enforce_sms_consent } } `; @@ -2968,3 +2969,103 @@ exports.GET_JOB_WATCHERS_MINIMAL = ` } } `; + +// Query to get consent status for a phone number +exports.GET_PHONE_NUMBER_CONSENT = ` + query GET_PHONE_NUMBER_CONSENT($bodyshopid: uuid!, $phone_number: String!) { + phone_number_consent(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) { + id + bodyshopid + phone_number + consent_status + created_at + updated_at + consent_updated_at + history(order_by: { changed_at: desc }) { + id + old_value + new_value + reason + changed_at + changed_by + } + } + } +`; + +// Query to get consent history +exports.GET_PHONE_NUMBER_CONSENT_HISTORY = ` + query GET_PHONE_NUMBER_CONSENT_HISTORY($phone_number_consent_id: uuid!) { + phone_number_consent_history(where: { phone_number_consent_id: { _eq: $phone_number_consent_id } }, order_by: { changed_at: desc }) { + id + phone_number_consent_id + old_value + new_value + reason + changed_at + changed_by + } + } +`; + +// Mutation to set consent status +exports.SET_PHONE_NUMBER_CONSENT = ` + mutation SET_PHONE_NUMBER_CONSENT($bodyshopid: uuid!, $phone_number: String!, $consent_status: Boolean!, $reason: String!) { + insert_phone_number_consent_one( + object: { + bodyshopid: $bodyshopid + phone_number: $phone_number + consent_status: $consent_status + consent_updated_at: "now()" + } + on_conflict: { + constraint: phone_number_consent_bodyshopid_phone_number_key + update_columns: [consent_status, consent_updated_at] + } + ) { + id + bodyshopid + phone_number + consent_status + created_at + updated_at + consent_updated_at + } + insert_phone_number_consent_history_one( + object: { + phone_number_consent_id: $id + old_value: $old_value + new_value: $consent_status + reason: $reason + changed_by: $changed_by + } + ) { + id + reason + changed_at + changed_by + } + } +`; + +// Mutation for bulk consent updates +exports.BULK_SET_PHONE_NUMBER_CONSENT = ` + mutation BULK_SET_PHONE_NUMBER_CONSENT($objects: [phone_number_consent_insert_input!]!) { + insert_phone_number_consent( + objects: $objects + on_conflict: { + constraint: phone_number_consent_bodyshopid_phone_number_key + update_columns: [consent_status, consent_updated_at] + } + ) { + affected_rows + returning { + id + bodyshopid + phone_number + consent_status + consent_updated_at + } + } + } +`; diff --git a/server/sms/receive.js b/server/sms/receive.js index f880105e9..128fda2ff 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -3,7 +3,8 @@ const { FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, UNARCHIVE_CONVERSATION, CREATE_CONVERSATION, - INSERT_MESSAGE + INSERT_MESSAGE, + SET_PHONE_NUMBER_CONSENT } = require("../graphql-client/queries"); const { phone } = require("phone"); const { admin } = require("../firebase/firebase-handler"); @@ -91,7 +92,30 @@ const receive = async (req, res) => { const bodyshop = response.bodyshops[0]; - // Sort conversations by `updated_at` (or `created_at`) and pick the last one + // Step 2: Handle consent + const normalizedPhone = phone(req.body.From, "CA").phoneNumber.replace(/^\+1/, ""); + const isStop = req.body.Body.toUpperCase().includes("STOP"); + const consentStatus = isStop ? false : true; + const reason = isStop ? "Customer texted STOP" : "Inbound message received"; + + const consentResponse = await client.request(SET_PHONE_NUMBER_CONSENT, { + bodyshopid: bodyshop.id, + phone_number: normalizedPhone, + consent_status: consentStatus, + reason, + changed_by: "system" + }); + + // Emit WebSocket event for consent change + const broadcastRoom = getBodyshopRoom(bodyshop.id); + ioRedis.to(broadcastRoom).emit("consent-changed", { + bodyshopId: bodyshop.id, + phone_number: normalizedPhone, + consent_status: consentStatus, + reason + }); + + // Step 3: Process conversation const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); const existingConversation = sortedConversations.length ? sortedConversations[sortedConversations.length - 1] @@ -104,14 +128,11 @@ const receive = async (req, res) => { image: !!req.body.MediaUrl0, image_path: generateMediaArray(req.body), isoutbound: false, - userid: null // Add additional fields as necessary + userid: null }; if (existingConversation) { - // Use the existing conversation conversationid = existingConversation.id; - - // Unarchive the conversation if necessary if (existingConversation.archived) { await client.request(UNARCHIVE_CONVERSATION, { id: conversationid, @@ -119,11 +140,10 @@ const receive = async (req, res) => { }); } } else { - // Create a new conversation const newConversationResponse = await client.request(CREATE_CONVERSATION, { conversation: { bodyshopid: bodyshop.id, - phone_num: phone(req.body.From).phoneNumber, + phone_num: normalizedPhone, archived: false } }); @@ -131,13 +151,12 @@ const receive = async (req, res) => { conversationid = createdConversation.id; } - // Ensure `conversationid` is added to the message newMessage.conversationid = conversationid; - // Step 3: Insert the message into the conversation + // Step 4: Insert the message const insertresp = await client.request(INSERT_MESSAGE, { msg: newMessage, - conversationid: conversationid + conversationid }); const message = insertresp?.insert_messages?.returning?.[0]; @@ -147,8 +166,7 @@ const receive = async (req, res) => { throw new Error("Conversation data is missing from the response."); } - // Step 4: Notify clients through Redis - const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id); + // Step 5: Notify clients const conversationRoom = getBodyshopConversationRoom({ bodyshopId: conversation.bodyshop.id, conversationId: conversation.id @@ -176,7 +194,7 @@ const receive = async (req, res) => { summary: false }); - // Step 5: Send FCM notification + // Step 6: Send FCM notification const fcmresp = await admin.messaging().send({ topic: `${message.conversation.bodyshop.imexshopid}-messaging`, notification: { From 7bd5190bf2f813bf5477b45297ee3b5108ba550f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 May 2025 18:19:39 -0400 Subject: [PATCH 13/22] feature/IO-3182-Phone-Number-Consent - Checkpoint --- .../chat-affix/chat-affix.container.jsx | 75 +++-- .../chat-conversation-list.component.jsx | 44 ++- .../chat-send-message.component.jsx | 13 +- .../phone-number-consent.component.jsx | 300 +++++++++++++----- .../shop-info/shop-info.consent.component.jsx | 58 ++-- client/src/graphql/consent.queries.js | 72 +---- client/src/redux/user/user.actions.js | 5 + client/src/redux/user/user.reducer.js | 9 +- client/src/redux/user/user.types.js | 3 +- hasura/metadata/tables.yaml | 2 +- .../down.sql | 1 + .../up.sql | 1 + server/graphql-client/queries.js | 87 +++-- server/routes/smsRoutes.js | 4 +- server/sms/consent.js | 215 +++++++++++++ server/sms/receive.js | 150 +++++---- server/sms/send.js | 53 +++- 17 files changed, 772 insertions(+), 320 deletions(-) create mode 100644 hasura/migrations/1747775597734_alter_table_public_phone_number_consent_history_alter_column_old_value/down.sql create mode 100644 hasura/migrations/1747775597734_alter_table_public_phone_number_consent_history_alter_column_old_value/up.sql create mode 100644 server/sms/consent.js diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx index cf8afce3b..85b06f2e8 100644 --- a/client/src/components/chat-affix/chat-affix.container.jsx +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -8,13 +8,15 @@ import ChatPopupComponent from "../chat-popup/chat-popup.component"; import "./chat-affix.styles.scss"; import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers"; import { useSocket } from "../../contexts/SocketIO/useSocket.js"; -import { GET_PHONE_NUMBER_CONSENT } from "../../graphql/consent.queries.js"; +import { GET_PHONE_NUMBER_CONSENT } from "../../graphql/consent.queries"; export function ChatAffixContainer({ bodyshop, chatVisible }) { const { t } = useTranslation(); const client = useApolloClient(); const { socket } = useSocket(); + const enforceConsent = bodyshop?.enforce_sms_consent ?? false; + useEffect(() => { if (!bodyshop || !bodyshop.messagingservicesid) return; @@ -39,45 +41,52 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) { if (socket && socket.connected) { registerMessagingHandlers({ socket, client }); - // Handle consent-changed events - const handleConsentChanged = ({ bodyshopId, phone_number, consent_status }) => { + // Handle consent-changed events only if enforce_sms_consent is true + const handleConsentChanged = ({ bodyshopId, phone_number, consent_status, reason }) => { + if (!enforceConsent || bodyshopId !== bodyshop.id) return; + try { - client.cache.writeQuery( + const cacheData = client.readQuery({ + query: GET_PHONE_NUMBER_CONSENT, + variables: { bodyshopid: bodyshopId, phone_number } + }); + + if (!cacheData?.phone_number_consent?.[0]) { + console.warn("No cached data for GET_PHONE_NUMBER_CONSENT:", { bodyshopId, phone_number }); + return; + } + + const updatedConsent = { + ...cacheData.phone_number_consent[0], + consent_status, + consent_updated_at: new Date().toISOString(), + phone_number_consent_history: [ + { + __typename: "phone_number_consent_history", + id: `temp-${Date.now()}`, + reason, + changed_at: new Date().toISOString(), + old_value: cacheData.phone_number_consent[0].consent_status, + new_value: consent_status, + changed_by: "system" + }, + ...(cacheData.phone_number_consent[0].phone_number_consent_history || []) + ] + }; + + client.writeQuery( { query: GET_PHONE_NUMBER_CONSENT, variables: { bodyshopid: bodyshopId, phone_number } }, - (data) => { - if (!data?.phone_number_consent?.[0]) { - return { - phone_number_consent: [ - { - __typename: "phone_number_consent", - id: null, - bodyshopid: bodyshopId, - phone_number, - consent_status, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - consent_updated_at: new Date().toISOString(), - history: [] - } - ] - }; - } - return { - phone_number_consent: [ - { - ...data.phone_number_consent[0], - consent_status, - consent_updated_at: new Date().toISOString() - } - ] - }; + { + phone_number_consent: [updatedConsent] } ); + + console.log("Cache update in handleConsentChanged:", { phone_number, consent_status, updatedConsent }); } catch (error) { - console.error("Error updating consent cache:", error); + console.error("Error updating consent cache in handleConsentChanged:", error.message, error.stack); } }; @@ -88,7 +97,7 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) { unregisterMessagingHandlers({ socket }); }; } - }, [bodyshop, socket, t, client]); + }, [bodyshop, socket, t, client, enforceConsent]); if (!bodyshop || !bodyshop.messagingservicesid) return <>; diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index ede2a2570..3bdc9cd28 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -1,5 +1,5 @@ import { Badge, Card, List, Space, Tag } from "antd"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState, useMemo } from "react"; import { connect } from "react-redux"; import { Virtuoso } from "react-virtuoso"; import { createStructuredSelector } from "reselect"; @@ -11,35 +11,37 @@ import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-displ import _ from "lodash"; import "./chat-conversation-list.styles.scss"; import { useQuery } from "@apollo/client"; -import { GET_PHONE_NUMBER_CONSENTS } from "../../graphql/consent.queries.js"; +import { GET_PHONE_NUMBER_CONSENTS } from "../../graphql/consent.queries"; import { phone } from "phone"; import { useTranslation } from "react-i18next"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - selectedConversation: selectSelectedConversation + selectedConversation: selectSelectedConversation, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId)) }); -function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) { +function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) { const { t } = useTranslation(); const [, forceUpdate] = useState(false); - // Normalize phone numbers and fetch consent statuses + const enforceConsent = bodyshop?.enforce_sms_consent ?? false; + const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "")); const { data: consentData, loading: consentLoading } = useQuery(GET_PHONE_NUMBER_CONSENTS, { variables: { bodyshopid: conversationList[0]?.bodyshopid, phone_numbers: phoneNumbers }, - skip: !conversationList.length || !conversationList[0]?.bodyshopid, + skip: !enforceConsent || !conversationList.length || !conversationList[0]?.bodyshopid, fetchPolicy: "cache-and-network" }); - // Create a map of phone number to consent status - const consentMap = React.useMemo(() => { + const consentMap = useMemo(() => { const map = new Map(); consentData?.phone_number_consent?.forEach((consent) => { map.set(consent.phone_number, consent.consent_status); @@ -54,14 +56,14 @@ function ChatConversationListComponent({ conversationList, selectedConversation, return () => clearInterval(interval); }, []); - const sortedConversationList = React.useMemo(() => { + const sortedConversationList = useMemo(() => { return _.orderBy(conversationList, ["updated_at"], ["desc"]); }, [conversationList]); const renderConversation = (index, t) => { const item = sortedConversationList[index]; const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); - const isConsented = consentMap.get(normalizedPhone) ?? false; + const isConsented = enforceConsent ? (consentMap.get(normalizedPhone) ?? false) : true; const cardContentRight = {item.updated_at}; const cardContentLeft = @@ -87,7 +89,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation, const cardExtra = ( <> - {!isConsented && {t("messaging.labels.no_consent")}} + {enforceConsent && !isConsented && {t("messaging.labels.no_consent")}} ); @@ -103,8 +105,24 @@ function ChatConversationListComponent({ conversationList, selectedConversation, className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`} > -
{cardContentLeft}
-
{cardContentRight}
+
+ {cardContentLeft} +
+
+ {cardContentRight} +
); diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index 29e5bb8c6..a42946113 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -1,5 +1,5 @@ import { LoadingOutlined, SendOutlined } from "@ant-design/icons"; -import { Input, Spin } from "antd"; +import { Input, Spin, Alert } from "antd"; import React, { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -31,12 +31,15 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi const [selectedMedia, setSelectedMedia] = useState([]); const { t } = useTranslation(); + const enforceConsent = bodyshop?.enforce_sms_consent ?? false; + const normalizedPhone = phone(conversation.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); const { data: consentData } = useQuery(GET_PHONE_NUMBER_CONSENT, { variables: { bodyshopid: bodyshop.id, phone_number: normalizedPhone }, - fetchPolicy: "cache-and-network" + fetchPolicy: "cache-and-network", + skip: !enforceConsent }); - const isConsented = consentData?.phone_number_consent?.[0]?.consent_status ?? false; + const isConsented = enforceConsent ? (consentData?.phone_number_consent?.[0]?.consent_status ?? false) : true; useEffect(() => { inputArea.current.focus(); @@ -69,8 +72,8 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi return (
- {!isConsented && ( - + {enforceConsent && !isConsented && ( + )} ({}); -function PhoneNumberConsentList({ bodyshop }) { +function PhoneNumberConsentList({ bodyshop, currentUser }) { const { t } = useTranslation(); const [search, setSearch] = useState(""); - const { loading, data } = useQuery(GET_PHONE_NUMBER_CONSENTS, { - variables: { bodyshopid: bodyshop.id, search }, + const notification = useNotification(); + const { loading, data, refetch } = useQuery(GET_PHONE_NUMBER_CONSENTS, { + variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }, fetchPolicy: "network-only" }); - const [setConsent] = useMutation(SET_PHONE_NUMBER_CONSENT); - const [bulkSetConsent] = useMutation(BULK_SET_PHONE_NUMBER_CONSENT); + const client = useApolloClient(); + const { socket } = useSocket(); + + useEffect(() => { + if (!socket || !socket.connected) return; + + const handleConsentChanged = ({ bodyshopId, phone_number, consent_status, reason }) => { + if (bodyshopId !== bodyshop.id) return; + + try { + const cacheData = client.readQuery({ + query: GET_PHONE_NUMBER_CONSENTS, + variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } + }); + + if (!cacheData?.phone_number_consent) { + console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in WebSocket handler"); + return; + } + + const updatedConsents = cacheData.phone_number_consent.map((consent) => + consent.phone_number === phone_number + ? { + ...consent, + consent_status, + consent_updated_at: new Date().toISOString(), + phone_number_consent_history: [ + { + __typename: "phone_number_consent_history", + id: `temp-${Date.now()}`, + reason, + changed_at: new Date().toISOString(), + old_value: consent.consent_status, + new_value: consent_status, + changed_by: currentUser.email + }, + ...(consent.phone_number_consent_history || []) + ] + } + : consent + ); + + client.writeQuery( + { + query: GET_PHONE_NUMBER_CONSENTS, + variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } + }, + { + phone_number_consent: updatedConsents + } + ); + + console.log("WebSocket cache update:", { phone_number, consent_status, updatedConsents }); + } catch (error) { + console.error("Error updating consent cache (WebSocket):", error.message, error.stack); + } + }; + + socket.on("consent-changed", handleConsentChanged); + + return () => { + socket.off("consent-changed", handleConsentChanged); + }; + }, [socket, client, bodyshop.id, search, currentUser.email]); + + const handleSetConsent = async (phone_number, consent_status) => { + try { + const response = await axios.post("/sms/setConsent", { + bodyshopid: bodyshop.id, + phone_number, + consent_status, + reason: "Manual override in app", + changed_by: currentUser.email + }); + + const updatedConsent = { + ...response.data.consent, + phone_number_consent_history: response.data.consent.phone_number_consent_history.map((history) => ({ + ...history, + __typename: "phone_number_consent_history" + })) + }; + + // Update Apollo cache + const cacheData = client.readQuery({ + query: GET_PHONE_NUMBER_CONSENTS, + variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } + }); + + let cacheUpdated = false; + if (cacheData?.phone_number_consent) { + const isPhoneNumberInCache = cacheData.phone_number_consent.some( + (consent) => consent.phone_number === phone_number + ); + + const updatedConsents = isPhoneNumberInCache + ? cacheData.phone_number_consent.map((consent) => + consent.phone_number === phone_number ? updatedConsent : consent + ) + : [...cacheData.phone_number_consent, updatedConsent]; + + cacheUpdated = client.writeQuery( + { + query: GET_PHONE_NUMBER_CONSENTS, + variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } + }, + { + phone_number_consent: updatedConsents + } + ); + + console.log("Cache update in handleSetConsent:", { + phone_number, + consent_status, + updatedConsents, + search + }); + } else { + console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in handleSetConsent"); + } + + // Always refetch to ensure UI updates + await refetch(); + + notification.success({ + message: t("consent.update_success") + }); + } catch (error) { + notification.error({ + message: t("consent.update_failed") + }); + console.error("Error updating consent:", error.message, error.stack); + } + }; + + const handleBulkUpload = async (file) => { + const reader = new FileReader(); + reader.onload = async (e) => { + const text = e.target.result; + const lines = text.split("\n").slice(1); // Skip header + const consents = lines + .filter((line) => line.trim()) + .map((line) => { + const [phone_number, consent_status] = line.split(","); + return { + phone_number: phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""), + consent_status: consent_status.trim().toLowerCase() === "true" + }; + }); + + try { + const response = await axios.post("/sms/bulkSetConsent", { + bodyshopid: bodyshop.id, + consents + }); + + const updatedConsents = response.data.consents.map((consent) => ({ + ...consent, + phone_number_consent_history: consent.phone_number_consent_history.map((history) => ({ + ...history, + __typename: "phone_number_consent_history" + })) + })); + + // Update Apollo cache + const cacheData = client.readQuery({ + query: GET_PHONE_NUMBER_CONSENTS, + variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } + }); + + if (cacheData?.phone_number_consent) { + const updatedConsentsMap = new Map(updatedConsents.map((consent) => [consent.phone_number, consent])); + + const mergedConsents = cacheData.phone_number_consent.map((consent) => + updatedConsentsMap.has(consent.phone_number) ? updatedConsentsMap.get(consent.phone_number) : consent + ); + + updatedConsents.forEach((consent) => { + if (!mergedConsents.some((c) => c.phone_number === consent.phone_number)) { + mergedConsents.push(consent); + } + }); + + client.writeQuery( + { + query: GET_PHONE_NUMBER_CONSENTS, + variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } + }, + { + phone_number_consent: mergedConsents + } + ); + + console.log("Cache update in handleBulkUpload:", { updatedConsents, mergedConsents }); + } else { + console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in handleBulkUpload"); + } + + // Refetch to ensure UI updates + await refetch(); + } catch (error) { + notification.error({ + message: t("consent.bulk_update_failed") + }); + console.error("Bulk upload failed:", error.message, error.stack); + } + }; + reader.readAsText(file); + return false; + }; + + if (!bodyshop?.enforce_sms_consent) return null; const columns = [ { title: t("consent.phone_number"), dataIndex: "phone_number", - render: (text) => {text} + render: (text) => {text}, + sorter: (a, b) => a.phone_number.localeCompare(b.phone_number) }, { title: t("consent.status"), dataIndex: "consent_status", render: (status, record) => ( - - - setConsent({ - variables: { - bodyshopid: bodyshop.id, - phone_number: record.phone_number, - consent_status: checked, - reason: "Manual override in app", - changed_by: "user" // Replace with actual user email from context - }, - optimisticResponse: { - insert_phone_number_consent_one: { - __typename: "phone_number_consent", - id: record.id, - bodyshopid: bodyshop.id, - phone_number: record.phone_number, - consent_status: checked, - created_at: record.created_at, - updated_at: new Date().toISOString(), - consent_updated_at: new Date().toISOString() - } - } - }) - } - /> + + handleSetConsent(record.phone_number, checked)} /> ) }, @@ -78,35 +265,6 @@ function PhoneNumberConsentList({ bodyshop }) { } ]; - const handleBulkUpload = async (file) => { - const reader = new FileReader(); - reader.onload = async (e) => { - const text = e.target.result; - const lines = text.split("\n").slice(1); // Skip header - const objects = lines - .filter((line) => line.trim()) - .map((line) => { - const [phone_number, consent_status] = line.split(","); - return { - bodyshopid: bodyshop.id, - phone_number: phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""), - consent_status: consent_status.trim().toLowerCase() === "true" - }; - }); - - try { - await bulkSetConsent({ - variables: { objects }, - context: { headers: { "x-reason": "System update via bulk upload", "x-changed-by": "system" } } - }); - } catch (error) { - console.error("Bulk upload failed:", error); - } - }; - reader.readAsText(file); - return false; - }; - return (
({}); +const mapDispatchToProps = (dispatch) => ({ + updateBodyshopEnforceConsent: (enforce_sms_consent) => dispatch(updateBodyshopEnforceConsent(enforce_sms_consent)) +}); -function ShopInfoConsentComponent({ bodyshop }) { +function ShopInfoConsentComponent({ bodyshop, updateBodyshopEnforceConsent }) { const { t } = useTranslation(); - const [updateEnforceConsent] = useMutation(UPDATE_BODYSHOP_ENFORCE_CONSENT); - - console.dir(bodyshop); + const [updateEnforceConsent] = useMutation(UPDATE_BODYSHOP_ENFORCE_CONSENT, { + onError: (error) => { + message.error(t("settings.enforce_sms_consent_error")); + console.error("Error updating enforce_sms_consent:", error); + }, + onCompleted: (data) => { + message.success(t("settings.enforce_sms_consent_success")); + updateBodyshopEnforceConsent(data.update_bodyshops_by_pk.enforce_sms_consent); + } + }); const enforceConsent = bodyshop?.enforce_sms_consent ?? false; @@ -27,23 +37,29 @@ function ShopInfoConsentComponent({ bodyshop }) { {t("settings.title")}
{t("settings.enforce_sms_consent")} - - updateEnforceConsent({ - variables: { id: bodyshop.id, enforce_sms_consent: checked }, - optimisticResponse: { - update_bodyshops_by_pk: { - __typename: "bodyshops", - id: bodyshop.id, - enforce_sms_consent: checked + + { + if (!checked && enforceConsent) return; // Prevent disabling + updateEnforceConsent({ + variables: { id: bodyshop.id, enforce_sms_consent: checked }, + optimisticResponse: { + update_bodyshops_by_pk: { + __typename: "bodyshops", + id: bodyshop.id, + enforce_sms_consent: checked + } } - } - }) - } - /> + }); + }} + disabled={enforceConsent} + /> +
- + {enforceConsent && }
); } diff --git a/client/src/graphql/consent.queries.js b/client/src/graphql/consent.queries.js index 8a3f78c8f..66b15f454 100644 --- a/client/src/graphql/consent.queries.js +++ b/client/src/graphql/consent.queries.js @@ -10,18 +10,23 @@ export const GET_PHONE_NUMBER_CONSENT = gql` created_at updated_at consent_updated_at - history(order_by: { changed_at: desc }, limit: 1) { + phone_number_consent_history(order_by: { changed_at: desc }, limit: 1) { + id reason + changed_at + old_value + new_value + changed_by } } } `; export const GET_PHONE_NUMBER_CONSENTS = gql` - query GET_PHONE_NUMBER_CONSENTS($bodyshopid: uuid!, $phone_numbers: [String!]) { + query GET_PHONE_NUMBER_CONSENTS($bodyshopid: uuid!, $search: String) { phone_number_consent( - where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _in: $phone_numbers } } - order_by: { consent_updated_at: desc } + where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _ilike: $search } } + order_by: [{ phone_number: asc }, { consent_updated_at: desc }] ) { id bodyshopid @@ -30,60 +35,13 @@ export const GET_PHONE_NUMBER_CONSENTS = gql` created_at updated_at consent_updated_at - history(order_by: { changed_at: desc }, limit: 1) { - reason - } - } - } -`; - -export const SET_PHONE_NUMBER_CONSENT = gql` - mutation SET_PHONE_NUMBER_CONSENT( - $bodyshopid: uuid! - $phone_number: String! - $consent_status: Boolean! - $reason: String! - $changed_by: String! - ) { - insert_phone_number_consent_one( - object: { - bodyshopid: $bodyshopid - phone_number: $phone_number - consent_status: $consent_status - consent_updated_at: "now()" - } - on_conflict: { - constraint: phone_number_consent_bodyshopid_phone_number_key - update_columns: [consent_status, consent_updated_at] - } - ) { - id - bodyshopid - phone_number - consent_status - created_at - updated_at - consent_updated_at - } - } -`; - -export const BULK_SET_PHONE_NUMBER_CONSENT = gql` - mutation BULK_SET_PHONE_NUMBER_CONSENT($objects: [phone_number_consent_insert_input!]!) { - insert_phone_number_consent( - objects: $objects - on_conflict: { - constraint: phone_number_consent_bodyshopid_phone_number_key - update_columns: [consent_status, consent_updated_at] - } - ) { - affected_rows - returning { + phone_number_consent_history(order_by: { changed_at: desc }, limit: 1) { id - bodyshopid - phone_number - consent_status - consent_updated_at + reason + changed_at + old_value + new_value + changed_by } } } diff --git a/client/src/redux/user/user.actions.js b/client/src/redux/user/user.actions.js index 01ba22534..125aab415 100644 --- a/client/src/redux/user/user.actions.js +++ b/client/src/redux/user/user.actions.js @@ -123,3 +123,8 @@ export const setImexShopId = (imexshopid) => ({ type: UserActionTypes.SET_IMEX_SHOP_ID, payload: imexshopid }); + +export const updateBodyshopEnforceConsent = (enforce_sms_consent) => ({ + type: UserActionTypes.UPDATE_BODYSHOP_ENFORCE_CONSENT, + payload: enforce_sms_consent +}); diff --git a/client/src/redux/user/user.reducer.js b/client/src/redux/user/user.reducer.js index 0042115ff..a72d4f068 100644 --- a/client/src/redux/user/user.reducer.js +++ b/client/src/redux/user/user.reducer.js @@ -105,7 +105,6 @@ const userReducer = (state = INITIAL_STATE, action) => { ...action.payload //Spread current user details in. } }; - case UserActionTypes.SET_SHOP_DETAILS: return { ...state, @@ -126,6 +125,14 @@ const userReducer = (state = INITIAL_STATE, action) => { ...state, imexshopid: action.payload }; + case UserActionTypes.UPDATE_BODYSHOP_ENFORCE_CONSENT: + return { + ...state, + bodyshop: { + ...state.bodyshop, + enforce_sms_consent: action.payload + } + }; default: return state; } diff --git a/client/src/redux/user/user.types.js b/client/src/redux/user/user.types.js index d9cd6fe62..ff21dbb5a 100644 --- a/client/src/redux/user/user.types.js +++ b/client/src/redux/user/user.types.js @@ -33,6 +33,7 @@ const UserActionTypes = { CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE", SET_CURRENT_EULA: "SET_CURRENT_EULA", EULA_ACCEPTED: "EULA_ACCEPTED", - SET_IMEX_SHOP_ID: "SET_IMEX_SHOP_ID" + SET_IMEX_SHOP_ID: "SET_IMEX_SHOP_ID", + UPDATE_BODYSHOP_ENFORCE_CONSENT: "UPDATE_BODYSHOP_ENFORCE_CONSENT" }; export default UserActionTypes; diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 17c162e07..fd83ecbc4 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -5871,7 +5871,7 @@ using: foreign_key_constraint_on: bodyshopid array_relationships: - - name: phone_number_consent_histories + - name: phone_number_consent_history using: foreign_key_constraint_on: column: phone_number_consent_id diff --git a/hasura/migrations/1747775597734_alter_table_public_phone_number_consent_history_alter_column_old_value/down.sql b/hasura/migrations/1747775597734_alter_table_public_phone_number_consent_history_alter_column_old_value/down.sql new file mode 100644 index 000000000..fb33566b8 --- /dev/null +++ b/hasura/migrations/1747775597734_alter_table_public_phone_number_consent_history_alter_column_old_value/down.sql @@ -0,0 +1 @@ +alter table "public"."phone_number_consent_history" alter column "old_value" set not null; diff --git a/hasura/migrations/1747775597734_alter_table_public_phone_number_consent_history_alter_column_old_value/up.sql b/hasura/migrations/1747775597734_alter_table_public_phone_number_consent_history_alter_column_old_value/up.sql new file mode 100644 index 000000000..406cf8efc --- /dev/null +++ b/hasura/migrations/1747775597734_alter_table_public_phone_number_consent_history_alter_column_old_value/up.sql @@ -0,0 +1 @@ +alter table "public"."phone_number_consent_history" alter column "old_value" drop not null; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 4ed9e5905..84e20db6f 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2970,7 +2970,7 @@ exports.GET_JOB_WATCHERS_MINIMAL = ` } `; -// Query to get consent status for a phone number +// Query to get consent status for a single phone number exports.GET_PHONE_NUMBER_CONSENT = ` query GET_PHONE_NUMBER_CONSENT($bodyshopid: uuid!, $phone_number: String!) { phone_number_consent(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) { @@ -2981,7 +2981,7 @@ exports.GET_PHONE_NUMBER_CONSENT = ` created_at updated_at consent_updated_at - history(order_by: { changed_at: desc }) { + phone_number_consent_history(order_by: { changed_at: desc }) { id old_value new_value @@ -2993,24 +2993,45 @@ exports.GET_PHONE_NUMBER_CONSENT = ` } `; -// Query to get consent history -exports.GET_PHONE_NUMBER_CONSENT_HISTORY = ` - query GET_PHONE_NUMBER_CONSENT_HISTORY($phone_number_consent_id: uuid!) { - phone_number_consent_history(where: { phone_number_consent_id: { _eq: $phone_number_consent_id } }, order_by: { changed_at: desc }) { +// Query to get consent statuses for multiple phone numbers +exports.GET_PHONE_NUMBER_CONSENTS = ` + query GET_PHONE_NUMBER_CONSENTS($bodyshopid: uuid!, $phone_numbers: [String!]) { + phone_number_consent(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _in: $phone_numbers } }) { id - phone_number_consent_id - old_value - new_value - reason - changed_at - changed_by + bodyshopid + phone_number + consent_status + created_at + updated_at + consent_updated_at + phone_number_consent_history(order_by: { changed_at: desc }) { + id + old_value + new_value + reason + changed_at + changed_by + } } } `; -// Mutation to set consent status +// Mutation to update enforce_sms_consent +exports.UPDATE_BODYSHOP_ENFORCE_CONSENT = ` + mutation UPDATE_BODYSHOP_ENFORCE_CONSENT($id: uuid!, $enforce_sms_consent: Boolean!) { + update_bodyshops_by_pk( + pk_columns: { id: $id } + _set: { enforce_sms_consent: $enforce_sms_consent } + ) { + id + enforce_sms_consent + } + } +`; + +// Mutation to set consent status for a single phone number exports.SET_PHONE_NUMBER_CONSENT = ` - mutation SET_PHONE_NUMBER_CONSENT($bodyshopid: uuid!, $phone_number: String!, $consent_status: Boolean!, $reason: String!) { + mutation SET_PHONE_NUMBER_CONSENT($bodyshopid: uuid!, $phone_number: String!, $consent_status: Boolean!) { insert_phone_number_consent_one( object: { bodyshopid: $bodyshopid @@ -3031,24 +3052,10 @@ exports.SET_PHONE_NUMBER_CONSENT = ` updated_at consent_updated_at } - insert_phone_number_consent_history_one( - object: { - phone_number_consent_id: $id - old_value: $old_value - new_value: $consent_status - reason: $reason - changed_by: $changed_by - } - ) { - id - reason - changed_at - changed_by - } } `; -// Mutation for bulk consent updates +// Mutation to set consent status for multiple phone numbers exports.BULK_SET_PHONE_NUMBER_CONSENT = ` mutation BULK_SET_PHONE_NUMBER_CONSENT($objects: [phone_number_consent_insert_input!]!) { insert_phone_number_consent( @@ -3064,8 +3071,30 @@ exports.BULK_SET_PHONE_NUMBER_CONSENT = ` bodyshopid phone_number consent_status + created_at + updated_at consent_updated_at } } } `; + +// Mutation to insert multiple consent history records +exports.INSERT_PHONE_NUMBER_CONSENT_HISTORY = ` + mutation INSERT_PHONE_NUMBER_CONSENT_HISTORY($objects: [phone_number_consent_history_insert_input!]!) { + insert_phone_number_consent_history( + objects: $objects + ) { + affected_rows + returning { + id + phone_number_consent_id + old_value + new_value + reason + changed_at + changed_by + } + } + } +`; diff --git a/server/routes/smsRoutes.js b/server/routes/smsRoutes.js index 1b169747d..917699aaf 100644 --- a/server/routes/smsRoutes.js +++ b/server/routes/smsRoutes.js @@ -5,14 +5,16 @@ const { receive } = require("../sms/receive"); const { send } = require("../sms/send"); const { status, markConversationRead } = require("../sms/status"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); +const { setConsent, bulkSetConsent } = require("../sms/consent"); // 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); router.post("/send", validateFirebaseIdTokenMiddleware, send); router.post("/status", twilioWebhookMiddleware, status); router.post("/markConversationRead", validateFirebaseIdTokenMiddleware, markConversationRead); +router.post("/setConsent", validateFirebaseIdTokenMiddleware, setConsent); +router.post("/bulkSetConsent", validateFirebaseIdTokenMiddleware, bulkSetConsent); module.exports = router; diff --git a/server/sms/consent.js b/server/sms/consent.js new file mode 100644 index 000000000..d7a997423 --- /dev/null +++ b/server/sms/consent.js @@ -0,0 +1,215 @@ +const { + SET_PHONE_NUMBER_CONSENT, + BULK_SET_PHONE_NUMBER_CONSENT, + INSERT_PHONE_NUMBER_CONSENT_HISTORY +} = require("../graphql-client/queries"); +const { phone } = require("phone"); +const gqlClient = require("../graphql-client/graphql-client").client; + +/** + * Set SMS consent for a phone number + * @param req + * @param res + * @returns {Promise<*>} + */ +const setConsent = async (req, res) => { + const { bodyshopid, phone_number, consent_status, reason, changed_by } = req.body; + const { + logger, + ioRedis, + ioHelpers: { getBodyshopRoom }, + sessionUtils: { getBodyshopFromRedis } + } = req; + + if (!bodyshopid || !phone_number || consent_status === undefined || !reason || !changed_by) { + logger.log("set-consent-error", "ERROR", req.user.email, null, { + type: "missing-parameters", + bodyshopid, + phone_number, + consent_status, + reason, + changed_by + }); + return res.status(400).json({ success: false, message: "Missing required parameter(s)." }); + } + + try { + // Check enforce_sms_consent + const bodyShopData = await getBodyshopFromRedis(bodyshopid); + const enforceConsent = bodyShopData?.enforce_sms_consent ?? false; + + if (!enforceConsent) { + logger.log("set-consent-error", "ERROR", req.user.email, null, { + type: "consent-not-enforced", + bodyshopid + }); + return res.status(403).json({ success: false, message: "SMS consent enforcement is not enabled." }); + } + + const normalizedPhone = phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""); + const consentResponse = await gqlClient.request(SET_PHONE_NUMBER_CONSENT, { + bodyshopid, + phone_number: normalizedPhone, + consent_status + }); + + const consent = consentResponse.insert_phone_number_consent_one; + + // Log audit history + const historyResponse = await gqlClient.request(INSERT_PHONE_NUMBER_CONSENT_HISTORY, { + objects: [ + { + phone_number_consent_id: consent.id, + old_value: null, // Not tracking old value + new_value: consent_status, + reason, + changed_by, + changed_at: "now()" + } + ] + }); + + const history = historyResponse.insert_phone_number_consent_history.returning[0]; + + // Emit WebSocket event + const broadcastRoom = getBodyshopRoom(bodyshopid); + ioRedis.to(broadcastRoom).emit("consent-changed", { + bodyshopId: bodyshopid, + phone_number: normalizedPhone, + consent_status, + reason + }); + + logger.log("set-consent-success", "DEBUG", req.user.email, null, { + bodyshopid, + phone_number: normalizedPhone, + consent_status + }); + + // Return both consent and history + res.status(200).json({ + success: true, + consent: { + ...consent, + phone_number_consent_history: [history] + } + }); + } catch (error) { + logger.log("set-consent-error", "ERROR", req.user.email, null, { + bodyshopid, + phone_number, + error: error.message, + stack: error.stack + }); + res.status(500).json({ success: false, message: "Failed to update consent status." }); + } +}; + +/** + * Bulk set SMS consent for multiple phone numbers + * @param req + * @param res + * @returns {Promise<*>} + */ +const bulkSetConsent = async (req, res) => { + const { bodyshopid, consents } = req.body; // consents: [{ phone_number, consent_status }] + const { + logger, + ioRedis, + ioHelpers: { getBodyshopRoom }, + sessionUtils: { getBodyshopFromRedis } + } = req; + + if (!bodyshopid || !Array.isArray(consents) || consents.length === 0) { + logger.log("bulk-set-consent-error", "ERROR", req.user.email, null, { + type: "missing-parameters", + bodyshopid, + consents + }); + return res.status(400).json({ success: false, message: "Missing or invalid parameters." }); + } + + try { + // Check enforce_sms_consent + const bodyShopData = await getBodyshopFromRedis(bodyshopid); + const enforceConsent = bodyShopData?.enforce_sms_consent ?? false; + + if (!enforceConsent) { + logger.log("bulk-set-consent-error", "ERROR", req.user.email, null, { + type: "consent-not-enforced", + bodyshopid + }); + return res.status(403).json({ success: false, message: "SMS consent enforcement is not enabled." }); + } + + const objects = consents.map(({ phone_number, consent_status }) => ({ + bodyshopid, + phone_number: phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""), + consent_status, + consent_updated_at: "now()" + })); + + // Insert or update phone_number_consent records + const consentResponse = await gqlClient.request(BULK_SET_PHONE_NUMBER_CONSENT, { + objects + }); + + const updatedConsents = consentResponse.insert_phone_number_consent.returning; + + // Log audit history + const historyObjects = updatedConsents.map((consent) => ({ + phone_number_consent_id: consent.id, + old_value: null, // Not tracking old value for bulk updates + new_value: consent.consent_status, + reason: "System update via bulk upload", + changed_by: "system", + changed_at: "now()" + })); + + const historyResponse = await gqlClient.request(INSERT_PHONE_NUMBER_CONSENT_HISTORY, { + objects: historyObjects + }); + + const history = historyResponse.insert_phone_number_consent_history.returning; + + // Combine consents with their history + const consentsWithhistory = updatedConsents.map((consent, index) => ({ + ...consent, + phone_number_consent_history: [history[index]] + })); + + // Emit WebSocket events for each consent change + const broadcastRoom = getBodyshopRoom(bodyshopid); + updatedConsents.forEach((consent) => { + ioRedis.to(broadcastRoom).emit("consent-changed", { + bodyshopId: bodyshopid, + phone_number: consent.phone_number, + consent_status: consent.consent_status, + reason: "System update via bulk upload" + }); + }); + + logger.log("bulk-set-consent-success", "DEBUG", req.user.email, null, { + bodyshopid, + updatedCount: updatedConsents.length + }); + + res.status(200).json({ + success: true, + updatedCount: updatedConsents.length, + consents: consentsWithhistory + }); + } catch (error) { + logger.log("bulk-set-consent-error", "ERROR", req.user.email, null, { + bodyshopid, + error: error.message, + stack: error.stack + }); + res.status(500).json({ success: false, message: "Failed to update consents." }); + } +}; + +module.exports = { + setConsent, + bulkSetConsent +}; diff --git a/server/sms/receive.js b/server/sms/receive.js index 128fda2ff..59c4b1b32 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -8,65 +8,27 @@ const { } = require("../graphql-client/queries"); const { phone } = require("phone"); const { admin } = require("../firebase/firebase-handler"); -const logger = require("../utils/logger"); const InstanceManager = require("../utils/instanceMgr").default; /** - * Generate an array of media URLs from the request body - * @param body - * @returns {null|*[]} - */ -const generateMediaArray = (body) => { - const { NumMedia } = body; - if (parseInt(NumMedia) > 0) { - const ret = []; - for (let i = 0; i < parseInt(NumMedia); i++) { - ret.push(body[`MediaUrl${i}`]); - } - return ret; - } else { - return null; - } -}; - -/** - * Handle errors during the message receiving process - * @param req - * @param error - * @param res - * @param context - */ -const handleError = (req, error, res, context) => { - logger.log("sms-inbound-error", "ERROR", "api", null, { - msid: req.body.SmsMessageSid, - text: req.body.Body, - image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), - messagingServiceSid: req.body.MessagingServiceSid, - context, - error - }); - - res.status(500).json({ error: error.message || "Internal Server Error" }); -}; - -/** - * Receive an inbound SMS message + * Receive SMS messages from Twilio and process them * @param req * @param res * @returns {Promise<*>} */ const receive = async (req, res) => { const { + logger, ioRedis, - ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }, + sessionUtils: { getBodyshopFromRedis } } = req; const loggerData = { msid: req.body.SmsMessageSid, text: req.body.Body, image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body) + image_path: generateMediaArray(req.body, logger) }; logger.log("sms-inbound", "DEBUG", "api", null, loggerData); @@ -92,30 +54,36 @@ const receive = async (req, res) => { const bodyshop = response.bodyshops[0]; - // Step 2: Handle consent - const normalizedPhone = phone(req.body.From, "CA").phoneNumber.replace(/^\+1/, ""); - const isStop = req.body.Body.toUpperCase().includes("STOP"); - const consentStatus = isStop ? false : true; - const reason = isStop ? "Customer texted STOP" : "Inbound message received"; + // Step 2: Check enforce_sms_consent + const bodyShopData = await getBodyshopFromRedis(bodyshopid); + const enforceConsent = bodyShopData?.enforce_sms_consent ?? false; - const consentResponse = await client.request(SET_PHONE_NUMBER_CONSENT, { - bodyshopid: bodyshop.id, - phone_number: normalizedPhone, - consent_status: consentStatus, - reason, - changed_by: "system" - }); + // Step 3: Handle consent only if enforce_sms_consent is true + if (enforceConsent) { + const normalizedPhone = phone(req.body.From, "CA").phoneNumber.replace(/^\+1/, ""); + const isStop = req.body.Body.toUpperCase().includes("STOP"); + const consentStatus = isStop ? false : true; + const reason = isStop ? "Customer texted STOP" : "Inbound message received"; - // Emit WebSocket event for consent change - const broadcastRoom = getBodyshopRoom(bodyshop.id); - ioRedis.to(broadcastRoom).emit("consent-changed", { - bodyshopId: bodyshop.id, - phone_number: normalizedPhone, - consent_status: consentStatus, - reason - }); + const consentResponse = await client.request(SET_PHONE_NUMBER_CONSENT, { + bodyshopid: bodyshop.id, + phone_number: normalizedPhone, + consent_status: consentStatus, + reason, + changed_by: "system" + }); - // Step 3: Process conversation + // Emit WebSocket event for consent change + const broadcastRoom = getBodyshopRoom(bodyshop.id); + ioRedis.to(broadcastRoom).emit("consent-changed", { + bodyshopId: bodyshop.id, + phone_number: normalizedPhone, + consent_status: consentStatus, + reason + }); + } + + // Step 4: Process conversation const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); const existingConversation = sortedConversations.length ? sortedConversations[sortedConversations.length - 1] @@ -126,7 +94,7 @@ const receive = async (req, res) => { msid: req.body.SmsMessageSid, text: req.body.Body, image: !!req.body.MediaUrl0, - image_path: generateMediaArray(req.body), + image_path: generateMediaArray(req.body, logger), isoutbound: false, userid: null }; @@ -143,7 +111,7 @@ const receive = async (req, res) => { const newConversationResponse = await client.request(CREATE_CONVERSATION, { conversation: { bodyshopid: bodyshop.id, - phone_num: normalizedPhone, + phone_num: phone(req.body.From).phoneNumber, archived: false } }); @@ -153,7 +121,7 @@ const receive = async (req, res) => { newMessage.conversationid = conversationid; - // Step 4: Insert the message + // Step 5: Insert the message const insertresp = await client.request(INSERT_MESSAGE, { msg: newMessage, conversationid @@ -166,7 +134,7 @@ const receive = async (req, res) => { throw new Error("Conversation data is missing from the response."); } - // Step 5: Notify clients + // Step 6: Notify clients const conversationRoom = getBodyshopConversationRoom({ bodyshopId: conversation.bodyshop.id, conversationId: conversation.id @@ -179,6 +147,7 @@ const receive = async (req, res) => { msid: message.sid }; + const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id); ioRedis.to(broadcastRoom).emit("new-message-summary", { ...commonPayload, existingConversation: !!existingConversation, @@ -194,7 +163,7 @@ const receive = async (req, res) => { summary: false }); - // Step 6: Send FCM notification + // Step 7: Send FCM notification const fcmresp = await admin.messaging().send({ topic: `${message.conversation.bodyshop.imexshopid}-messaging`, notification: { @@ -220,10 +189,51 @@ const receive = async (req, res) => { res.status(200).send(""); } catch (e) { - handleError(req, e, res, "RECEIVE_MESSAGE"); + handleError(req, e, res, "RECEIVE_MESSAGE", logger); } }; +/** + * Generate media array from the request body + * @param body + * @param logger + * @returns {null|*[]} + */ +const generateMediaArray = (body, logger) => { + const { NumMedia } = body; + if (parseInt(NumMedia) > 0) { + const ret = []; + for (let i = 0; i < parseInt(NumMedia); i++) { + ret.push(body[`MediaUrl${i}`]); + } + return ret; + } else { + return null; + } +}; + +/** + * Handle error logging and response + * @param req + * @param error + * @param res + * @param context + * @param logger + */ +const handleError = (req, error, res, context, logger) => { + logger.log("sms-inbound-error", "ERROR", "api", null, { + msid: req.body.SmsMessageSid, + text: req.body.Body, + image: !!req.body.MediaUrl0, + image_path: generateMediaArray(req.body, logger), + messagingServiceSid: req.body.MessagingServiceSid, + context, + error + }); + + res.status(500).json({ error: error.message || "Internal Server Error" }); +}; + module.exports = { receive }; diff --git a/server/sms/send.js b/server/sms/send.js index bc0a95da9..81b2d4d6d 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -1,21 +1,16 @@ const twilio = require("twilio"); const { phone } = require("phone"); -const { INSERT_MESSAGE } = require("../graphql-client/queries"); -const logger = require("../utils/logger"); +const { INSERT_MESSAGE, GET_PHONE_NUMBER_CONSENT } = require("../graphql-client/queries"); const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); const gqlClient = require("../graphql-client/graphql-client").client; -/** - * Send an outbound SMS message - * @param req - * @param res - * @returns {Promise} - */ const send = async (req, res) => { - const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body; + const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid, bodyshopid } = req.body; const { ioRedis, - ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } + logger, + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }, + sessionUtils: { getBodyshopFromRedis } } = req; logger.log("sms-outbound", "DEBUG", req.user.email, null, { @@ -26,11 +21,11 @@ const send = async (req, res) => { conversationid, isoutbound: true, userid: req.user.email, - image: req.body.selectedMedia.length > 0, - image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] + image: selectedMedia.length > 0, + image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] }); - if (!to || !messagingServiceSid || (!body && selectedMedia.length === 0) || !conversationid) { + if (!to || !messagingServiceSid || (!body && selectedMedia.length === 0) || !conversationid || !bodyshopid) { logger.log("sms-outbound-error", "ERROR", req.user.email, null, { type: "missing-parameters", messagingServiceSid, @@ -39,14 +34,38 @@ const send = async (req, res) => { conversationid, isoutbound: true, userid: req.user.email, - image: req.body.selectedMedia.length > 0, - image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] + image: selectedMedia.length > 0, + image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] }); res.status(400).json({ success: false, message: "Missing required parameter(s)." }); return; } try { + // Check bodyshop's enforce_sms_consent setting + const bodyShopData = await getBodyshopFromRedis(bodyshopid); + const enforceConsent = bodyShopData?.enforce_sms_consent ?? false; + + // Check consent only if enforcement is enabled + if (enforceConsent) { + const normalizedPhone = phone(to, "CA").phoneNumber.replace(/^\+1/, ""); + const consentResponse = await gqlClient.request(GET_PHONE_NUMBER_CONSENT, { + bodyshopid, + phone_number: normalizedPhone + }); + if (!consentResponse.phone_number_consent?.length || !consentResponse.phone_number_consent[0].consent_status) { + logger.log("sms-outbound-error", "ERROR", req.user.email, null, { + type: "no-consent", + phone_number: normalizedPhone, + conversationid + }); + return res.status(403).json({ + success: false, + message: "Phone number has not consented to messaging." + }); + } + } + const message = await client.messages.create({ body, messagingServiceSid, @@ -60,8 +79,8 @@ const send = async (req, res) => { conversationid, isoutbound: true, userid: req.user.email, - image: req.body.selectedMedia.length > 0, - image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] + image: selectedMedia.length > 0, + image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] }; try { From 831802f5af7681bfe1607d1a59bd672201ea52e1 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 20 May 2025 15:25:29 -0700 Subject: [PATCH 14/22] IO-3235 FeatureAccess on VisualBoard for SmartSchedule Option of Color Cards Signed-off-by: Allan Carr --- .../settings/LayoutSettings.jsx | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/client/src/components/production-board-kanban/settings/LayoutSettings.jsx b/client/src/components/production-board-kanban/settings/LayoutSettings.jsx index 888503df2..8155a4ace 100644 --- a/client/src/components/production-board-kanban/settings/LayoutSettings.jsx +++ b/client/src/components/production-board-kanban/settings/LayoutSettings.jsx @@ -1,7 +1,15 @@ import { Card, Col, Form, Radio, Row } from "antd"; import PropTypes from "prop-types"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../../redux/user/user.selectors"; +import { HasFeatureAccess } from "../../feature-wrapper/feature-wrapper.component"; -const LayoutSettings = ({ t }) => ( +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const LayoutSettings = ({ t, bodyshop }) => ( {[ @@ -30,14 +38,18 @@ const LayoutSettings = ({ t }) => ( { value: false, label: t("production.labels.wide") } ] }, - { - name: "cardcolor", - label: t("production.labels.cardcolor"), - options: [ - { value: true, label: t("production.labels.on") }, - { value: false, label: t("production.labels.off") } - ] - }, + ...(HasFeatureAccess({ bodyshop, featureName: "smartscheduling" }) + ? [ + { + name: "cardcolor", + label: t("production.labels.cardcolor"), + options: [ + { value: true, label: t("production.labels.on") }, + { value: false, label: t("production.labels.off") } + ] + } + ] + : []), { name: "kiosk", label: t("production.labels.kiosk_mode"), @@ -67,4 +79,4 @@ LayoutSettings.propTypes = { t: PropTypes.func.isRequired }; -export default LayoutSettings; +export default connect(mapStateToProps)(LayoutSettings); From 079dffce4d6c31abc6b36ef8244b114bec0ec209 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 20 May 2025 17:16:27 -0700 Subject: [PATCH 15/22] IO-3236 HasFeatureAccess Date Signed-off-by: Allan Carr --- .../components/feature-wrapper/feature-wrapper.component.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/components/feature-wrapper/feature-wrapper.component.jsx b/client/src/components/feature-wrapper/feature-wrapper.component.jsx index 7dabea2ac..507a05923 100644 --- a/client/src/components/feature-wrapper/feature-wrapper.component.jsx +++ b/client/src/components/feature-wrapper/feature-wrapper.component.jsx @@ -81,8 +81,9 @@ export function HasFeatureAccess({ featureName, bodyshop, bypass, debug = false } return ( bodyshop?.features?.allAccess || - bodyshop?.features?.[featureName] || - dayjs(bodyshop?.features[featureName]).isAfter(dayjs()) + (typeof bodyshop?.features?.[featureName] === "boolean" + ? bodyshop?.features?.[featureName] + : dayjs(bodyshop?.features?.[featureName]).isAfter(dayjs())) ); } From 8ee52598e80a1ca68b2961e6bf622fe30bb6bd7d Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 May 2025 14:32:35 -0400 Subject: [PATCH 16/22] feature/IO-3182-Phone-Number-Consent - Checkpoint --- client/package-lock.json | 104 ++++---- client/package.json | 6 +- .../chat-affix/chat-affix.container.jsx | 57 +---- .../chat-conversation-list.component.jsx | 81 ++---- .../chat-send-message.component.jsx | 15 +- .../phone-number-consent.component.jsx | 231 +----------------- .../shop-info/shop-info.consent.component.jsx | 50 +--- client/src/graphql/bodyshop.queries.js | 10 - client/src/redux/user/user.actions.js | 5 - client/src/redux/user/user.reducer.js | 9 +- client/src/redux/user/user.types.js | 3 +- client/src/translations/en_us/common.json | 15 +- hasura/metadata/tables.yaml | 96 +------- .../down.sql | 3 + .../up.sql | 1 + .../down.sql | 2 + .../up.sql | 1 + .../down.sql | 3 + .../up.sql | 1 + .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 2 + .../up.sql | 1 + package-lock.json | 8 +- package.json | 2 +- server/graphql-client/queries.js | 130 ---------- server/routes/smsRoutes.js | 3 - server/sms/consent.js | 215 ---------------- server/sms/receive.js | 36 +-- server/sms/send.js | 26 +- server/sms/status.js | 1 + 31 files changed, 128 insertions(+), 991 deletions(-) create mode 100644 hasura/migrations/1747850002182_drop_table_public_phone_number_consent_history/down.sql create mode 100644 hasura/migrations/1747850002182_drop_table_public_phone_number_consent_history/up.sql create mode 100644 hasura/migrations/1747850205206_alter_table_public_phone_number_consent_drop_column_consent_status/down.sql create mode 100644 hasura/migrations/1747850205206_alter_table_public_phone_number_consent_drop_column_consent_status/up.sql create mode 100644 hasura/migrations/1747850221535_alter_table_public_phone_number_consent_drop_column_consent_updated_at/down.sql create mode 100644 hasura/migrations/1747850221535_alter_table_public_phone_number_consent_drop_column_consent_updated_at/up.sql create mode 100644 hasura/migrations/1747850386584_rename_table_public_phone_number_consent/down.sql create mode 100644 hasura/migrations/1747850386584_rename_table_public_phone_number_consent/up.sql create mode 100644 hasura/migrations/1747850458206_alter_table_public_bodyshops_drop_column_enforce_sms_consent/down.sql create mode 100644 hasura/migrations/1747850458206_alter_table_public_bodyshops_drop_column_enforce_sms_consent/up.sql delete mode 100644 server/sms/consent.js diff --git a/client/package-lock.json b/client/package-lock.json index 2939dbd7c..12ba5dfb3 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,8 +21,8 @@ "@jsreport/browser-client": "^3.1.0", "@reduxjs/toolkit": "^2.8.2", "@sentry/cli": "^2.45.0", - "@sentry/react": "^9.21.0", - "@sentry/vite-plugin": "^3.4.0", + "@sentry/react": "^9.22.0", + "@sentry/vite-plugin": "^3.5.0", "@splitsoftware/splitio-react": "^2.1.1", "@tanem/react-nprogress": "^5.0.53", "antd": "^5.25.2", @@ -95,7 +95,7 @@ "@emotion/react": "^11.14.0", "@eslint/js": "^9.27.0", "@playwright/test": "^1.51.1", - "@sentry/webpack-plugin": "^3.4.0", + "@sentry/webpack-plugin": "^3.5.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -4461,88 +4461,88 @@ "license": "MIT" }, "node_modules/@sentry-internal/browser-utils": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.21.0.tgz", - "integrity": "sha512-/lJ5EVUDbsVsPH/sSXwWBERVtzi4kWYeFLc+u+1zr4NrfDrGnPJ5mVS1VlHwtBmYIIWv8harLP+CReg3nDcXdw==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.22.0.tgz", + "integrity": "sha512-Ou1tBnVxFAIn8i9gvrWzRotNJQYiu3awNXpsFCw6qFwmiKAVPa6b13vCdolhXnrIiuR77jY1LQnKh9hXpoRzsg==", "license": "MIT", "dependencies": { - "@sentry/core": "9.21.0" + "@sentry/core": "9.22.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/feedback": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.21.0.tgz", - "integrity": "sha512-Z234NgcWolFpmztCh+9smC6WlO8By5t4KucHNfYSQ0xQYQCxPL5iChj3JpF4dwv+qCYXhDFLQFQbK0U3Px056g==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.22.0.tgz", + "integrity": "sha512-zgMVkoC61fgi41zLcSZA59vOtKxcLrKBo1ECYhPD1hxEaneNqY5fhXDwlQBw96P5l2yqkgfX6YZtSdU4ejI9yA==", "license": "MIT", "dependencies": { - "@sentry/core": "9.21.0" + "@sentry/core": "9.22.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.21.0.tgz", - "integrity": "sha512-7mq3Bsp8EJa3YTIYgmWfNgJdvbeaAJ6VYsqi0yxR/vNGxY3qH+PLlv+ZOEXI2U0CL6vhqFPbqmxiUOCuAjnpGg==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.22.0.tgz", + "integrity": "sha512-9GOycoKbrclcRXfcbNV8svbmAsOS5R4wXBQmKF4pFLkmFA/lJv9kdZSNYkRvkrxdNfbMIJXP+DV9EqTZcryXig==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.21.0", - "@sentry/core": "9.21.0" + "@sentry-internal/browser-utils": "9.22.0", + "@sentry/core": "9.22.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.21.0.tgz", - "integrity": "sha512-4tHiNil8qXphaql2YXLGA/wlm0hxaadrh7x8/KErn1iy3vJpn7t/Kka5uug7c2UWhtveS6dgGmqjSkDxM5h9bA==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.22.0.tgz", + "integrity": "sha512-EcG9IMSEalFe49kowBTJObWjof/iHteDwpyuAszsFDdQUYATrVUtwpwN7o52vDYWJud4arhjrQnMamIGxa79eQ==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "9.21.0", - "@sentry/core": "9.21.0" + "@sentry-internal/replay": "9.22.0", + "@sentry/core": "9.22.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/babel-plugin-component-annotate": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.4.0.tgz", - "integrity": "sha512-tSzfc3aE7m0PM0Aj7HBDet5llH9AB9oc+tBQ8AvOqUSnWodLrNCuWeQszJ7mIBovD3figgCU3h0cvI6U5cDtsg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.5.0.tgz", + "integrity": "sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==", "license": "MIT", "engines": { "node": ">= 14" } }, "node_modules/@sentry/browser": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.21.0.tgz", - "integrity": "sha512-NF0G104JRP2TZ2hpMHElO4bEEUdBWknKSh2d0SRyGpJFVfOQG3oRHczXWH08A5InA/lNrS9LEdodUhiFue+F3A==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.22.0.tgz", + "integrity": "sha512-3TeRm74dvX0JdjX0AgkQa+22iUHwHnY+Q6M05NZ+tDeCNHGK/mEBTeqquS1oQX67jWyuvYmG3VV6RJUxtG9Paw==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.21.0", - "@sentry-internal/feedback": "9.21.0", - "@sentry-internal/replay": "9.21.0", - "@sentry-internal/replay-canvas": "9.21.0", - "@sentry/core": "9.21.0" + "@sentry-internal/browser-utils": "9.22.0", + "@sentry-internal/feedback": "9.22.0", + "@sentry-internal/replay": "9.22.0", + "@sentry-internal/replay-canvas": "9.22.0", + "@sentry/core": "9.22.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/bundler-plugin-core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.4.0.tgz", - "integrity": "sha512-X1Q41AsQ6xcT6hB4wYmBDBukndKM/inT4IsR7pdKLi7ICpX2Qq6lisamBAEPCgEvnLpazSFguaiC0uiwMKAdqw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.5.0.tgz", + "integrity": "sha512-zDzPrhJqAAy2VzV4g540qAZH4qxzisstK2+NIJPZUUKztWRWUV2cMHsyUtdctYgloGkLyGpZJBE3RE6dmP/xqQ==", "license": "MIT", "dependencies": { "@babel/core": "^7.18.5", - "@sentry/babel-plugin-component-annotate": "3.4.0", + "@sentry/babel-plugin-component-annotate": "3.5.0", "@sentry/cli": "2.42.2", "dotenv": "^16.3.1", "find-up": "^5.0.0", @@ -4902,22 +4902,22 @@ } }, "node_modules/@sentry/core": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.21.0.tgz", - "integrity": "sha512-K0a72Evg0fzc52Oe8R8Op5TyUMzORkk4ytt3G24lSnF4hh8NPf0m6VGkEUgQRPj27g2bF6tq9fCNsJILsf1PDA==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.22.0.tgz", + "integrity": "sha512-ixvtKmPF42Y6ckGUbFlB54OWI75H2gO5UYHojO6eXFpS7xO3ZGgV/QH6wb40mWK+0w5XZ0233FuU9VpsuE6mKA==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@sentry/react": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.21.0.tgz", - "integrity": "sha512-RGbyVo4fS7SX2AjEpdRXDo4C4IYIx0zQcI5bSTgySuhxL0JAxohcuSsNWpx48QkJwK/avtmlmCIPKgbvhF16TQ==", + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.22.0.tgz", + "integrity": "sha512-mI43NnioBYdG5TiXqRlhV1feZs9bnrrl+k5HOHBK7VQtymaXO0fkcsRLZTkdSgLRLMJGasZuvVhq2xK+18QyWQ==", "license": "MIT", "dependencies": { - "@sentry/browser": "9.21.0", - "@sentry/core": "9.21.0", + "@sentry/browser": "9.22.0", + "@sentry/core": "9.22.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -4928,12 +4928,12 @@ } }, "node_modules/@sentry/vite-plugin": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-3.4.0.tgz", - "integrity": "sha512-pUFBGrKsHuc8K6A7B1wU2nx65n9aIzvTlcHX9yZ1qvjEO0cZFih0JCwu1Fcav/yrtT9RMN44L/ugu/kMBHQhjQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-3.5.0.tgz", + "integrity": "sha512-jUnpTdpicG8wefamw7eNo2uO+Q3KCbOAiF76xH4gfNHSW6TN2hBfOtmLu7J+ive4c0Al3+NEHz19bIPR0lkwWg==", "license": "MIT", "dependencies": { - "@sentry/bundler-plugin-core": "3.4.0", + "@sentry/bundler-plugin-core": "3.5.0", "unplugin": "1.0.1" }, "engines": { @@ -4941,13 +4941,13 @@ } }, "node_modules/@sentry/webpack-plugin": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-3.4.0.tgz", - "integrity": "sha512-i+nAxxniJV5ovijojjTF5n+Yj08Xk8my+vm8+oo0C0I7xcnI2gOKft6B0sJOq01CNbo85X5m/3/edL0PKoWE9w==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-3.5.0.tgz", + "integrity": "sha512-xvclj0QY2HyU7uJLzOlHSrZQBDwfnGKJxp8mmlU4L7CwmK+8xMCqlO7tYZoqE4K/wU3c2xpXql70x8qmvNMxzQ==", "dev": true, "license": "MIT", "dependencies": { - "@sentry/bundler-plugin-core": "3.4.0", + "@sentry/bundler-plugin-core": "3.5.0", "unplugin": "1.0.1", "uuid": "^9.0.0" }, diff --git a/client/package.json b/client/package.json index c6bc4f515..40c52b409 100644 --- a/client/package.json +++ b/client/package.json @@ -20,8 +20,8 @@ "@jsreport/browser-client": "^3.1.0", "@reduxjs/toolkit": "^2.8.2", "@sentry/cli": "^2.45.0", - "@sentry/react": "^9.21.0", - "@sentry/vite-plugin": "^3.4.0", + "@sentry/react": "^9.22.0", + "@sentry/vite-plugin": "^3.5.0", "@splitsoftware/splitio-react": "^2.1.1", "@tanem/react-nprogress": "^5.0.53", "antd": "^5.25.2", @@ -135,7 +135,7 @@ "@emotion/react": "^11.14.0", "@eslint/js": "^9.27.0", "@playwright/test": "^1.51.1", - "@sentry/webpack-plugin": "^3.4.0", + "@sentry/webpack-plugin": "^3.5.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx index 85b06f2e8..b370b221c 100644 --- a/client/src/components/chat-affix/chat-affix.container.jsx +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -8,15 +8,12 @@ import ChatPopupComponent from "../chat-popup/chat-popup.component"; import "./chat-affix.styles.scss"; import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers"; import { useSocket } from "../../contexts/SocketIO/useSocket.js"; -import { GET_PHONE_NUMBER_CONSENT } from "../../graphql/consent.queries"; export function ChatAffixContainer({ bodyshop, chatVisible }) { const { t } = useTranslation(); const client = useApolloClient(); const { socket } = useSocket(); - const enforceConsent = bodyshop?.enforce_sms_consent ?? false; - useEffect(() => { if (!bodyshop || !bodyshop.messagingservicesid) return; @@ -41,63 +38,11 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) { if (socket && socket.connected) { registerMessagingHandlers({ socket, client }); - // Handle consent-changed events only if enforce_sms_consent is true - const handleConsentChanged = ({ bodyshopId, phone_number, consent_status, reason }) => { - if (!enforceConsent || bodyshopId !== bodyshop.id) return; - - try { - const cacheData = client.readQuery({ - query: GET_PHONE_NUMBER_CONSENT, - variables: { bodyshopid: bodyshopId, phone_number } - }); - - if (!cacheData?.phone_number_consent?.[0]) { - console.warn("No cached data for GET_PHONE_NUMBER_CONSENT:", { bodyshopId, phone_number }); - return; - } - - const updatedConsent = { - ...cacheData.phone_number_consent[0], - consent_status, - consent_updated_at: new Date().toISOString(), - phone_number_consent_history: [ - { - __typename: "phone_number_consent_history", - id: `temp-${Date.now()}`, - reason, - changed_at: new Date().toISOString(), - old_value: cacheData.phone_number_consent[0].consent_status, - new_value: consent_status, - changed_by: "system" - }, - ...(cacheData.phone_number_consent[0].phone_number_consent_history || []) - ] - }; - - client.writeQuery( - { - query: GET_PHONE_NUMBER_CONSENT, - variables: { bodyshopid: bodyshopId, phone_number } - }, - { - phone_number_consent: [updatedConsent] - } - ); - - console.log("Cache update in handleConsentChanged:", { phone_number, consent_status, updatedConsent }); - } catch (error) { - console.error("Error updating consent cache in handleConsentChanged:", error.message, error.stack); - } - }; - - socket.on("consent-changed", handleConsentChanged); - return () => { - socket.off("consent-changed", handleConsentChanged); unregisterMessagingHandlers({ socket }); }; } - }, [bodyshop, socket, t, client, enforceConsent]); + }, [bodyshop, socket, t, client]); if (!bodyshop || !bodyshop.messagingservicesid) return <>; diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index 3bdc9cd28..16d4c0bf1 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -1,5 +1,5 @@ import { Badge, Card, List, Space, Tag } from "antd"; -import { useEffect, useState, useMemo } from "react"; +import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import { Virtuoso } from "react-virtuoso"; import { createStructuredSelector } from "reselect"; @@ -10,61 +10,35 @@ import PhoneFormatter from "../../utils/PhoneFormatter"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import _ from "lodash"; import "./chat-conversation-list.styles.scss"; -import { useQuery } from "@apollo/client"; -import { GET_PHONE_NUMBER_CONSENTS } from "../../graphql/consent.queries"; -import { phone } from "phone"; -import { useTranslation } from "react-i18next"; -import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - selectedConversation: selectSelectedConversation, - bodyshop: selectBodyshop + selectedConversation: selectSelectedConversation }); const mapDispatchToProps = (dispatch) => ({ setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId)) }); -function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) { - const { t } = useTranslation(); +function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) { + // That comma is there for a reason, do not remove it const [, forceUpdate] = useState(false); - const enforceConsent = bodyshop?.enforce_sms_consent ?? false; - - const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "")); - const { data: consentData, loading: consentLoading } = useQuery(GET_PHONE_NUMBER_CONSENTS, { - variables: { - bodyshopid: conversationList[0]?.bodyshopid, - phone_numbers: phoneNumbers - }, - skip: !enforceConsent || !conversationList.length || !conversationList[0]?.bodyshopid, - fetchPolicy: "cache-and-network" - }); - - const consentMap = useMemo(() => { - const map = new Map(); - consentData?.phone_number_consent?.forEach((consent) => { - map.set(consent.phone_number, consent.consent_status); - }); - return map; - }, [consentData]); - + // Re-render every minute useEffect(() => { const interval = setInterval(() => { - forceUpdate((prev) => !prev); - }, 60000); - return () => clearInterval(interval); + forceUpdate((prev) => !prev); // Toggle state to trigger re-render + }, 60000); // 1 minute in milliseconds + + return () => clearInterval(interval); // Cleanup on unmount }, []); - const sortedConversationList = useMemo(() => { + // Memoize the sorted conversation list + const sortedConversationList = React.useMemo(() => { return _.orderBy(conversationList, ["updated_at"], ["desc"]); }, [conversationList]); - const renderConversation = (index, t) => { + const renderConversation = (index) => { const item = sortedConversationList[index]; - const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); - const isConsented = enforceConsent ? (consentMap.get(normalizedPhone) ?? false) : true; - const cardContentRight = {item.updated_at}; const cardContentLeft = item.job_conversations.length > 0 @@ -86,12 +60,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation, ); - const cardExtra = ( - <> - - {enforceConsent && !isConsented && {t("messaging.labels.no_consent")}} - - ); + const cardExtra = ; const getCardStyle = () => item.id === selectedConversation @@ -104,25 +73,9 @@ function ChatConversationListComponent({ conversationList, selectedConversation, onClick={() => setSelectedConversation(item.id)} className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`} > - -
- {cardContentLeft} -
-
- {cardContentRight} -
+ +
{cardContentLeft}
+
{cardContentRight}
); @@ -132,7 +85,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
renderConversation(index, t)} + itemContent={(index) => renderConversation(index)} style={{ height: "100%", width: "100%" }} />
diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index a42946113..aa5b3f035 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -1,5 +1,5 @@ import { LoadingOutlined, SendOutlined } from "@ant-design/icons"; -import { Input, Spin, Alert } from "antd"; +import { Alert, Input, Spin } from "antd"; import React, { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -12,7 +12,6 @@ import ChatMediaSelector from "../chat-media-selector/chat-media-selector.compon import ChatPresetsComponent from "../chat-presets/chat-presets.component"; import { useQuery } from "@apollo/client"; import { GET_PHONE_NUMBER_CONSENT } from "../../graphql/consent.queries"; -import AlertComponent from "../alert/alert.component"; import { phone } from "phone"; const mapStateToProps = createStructuredSelector({ @@ -31,15 +30,13 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi const [selectedMedia, setSelectedMedia] = useState([]); const { t } = useTranslation(); - const enforceConsent = bodyshop?.enforce_sms_consent ?? false; - const normalizedPhone = phone(conversation.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); const { data: consentData } = useQuery(GET_PHONE_NUMBER_CONSENT, { variables: { bodyshopid: bodyshop.id, phone_number: normalizedPhone }, - fetchPolicy: "cache-and-network", - skip: !enforceConsent + fetchPolicy: "cache-and-network" }); - const isConsented = enforceConsent ? (consentData?.phone_number_consent?.[0]?.consent_status ?? false) : true; + + const isConsented = consentData?.phone_number_consent?.[0]?.consent_status ?? false; useEffect(() => { inputArea.current.focus(); @@ -72,9 +69,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi return (
- {enforceConsent && !isConsented && ( - - )} + {!isConsented && } { - if (!socket || !socket.connected) return; - - const handleConsentChanged = ({ bodyshopId, phone_number, consent_status, reason }) => { - if (bodyshopId !== bodyshop.id) return; - - try { - const cacheData = client.readQuery({ - query: GET_PHONE_NUMBER_CONSENTS, - variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } - }); - - if (!cacheData?.phone_number_consent) { - console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in WebSocket handler"); - return; - } - - const updatedConsents = cacheData.phone_number_consent.map((consent) => - consent.phone_number === phone_number - ? { - ...consent, - consent_status, - consent_updated_at: new Date().toISOString(), - phone_number_consent_history: [ - { - __typename: "phone_number_consent_history", - id: `temp-${Date.now()}`, - reason, - changed_at: new Date().toISOString(), - old_value: consent.consent_status, - new_value: consent_status, - changed_by: currentUser.email - }, - ...(consent.phone_number_consent_history || []) - ] - } - : consent - ); - - client.writeQuery( - { - query: GET_PHONE_NUMBER_CONSENTS, - variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } - }, - { - phone_number_consent: updatedConsents - } - ); - - console.log("WebSocket cache update:", { phone_number, consent_status, updatedConsents }); - } catch (error) { - console.error("Error updating consent cache (WebSocket):", error.message, error.stack); - } - }; - - socket.on("consent-changed", handleConsentChanged); - - return () => { - socket.off("consent-changed", handleConsentChanged); - }; - }, [socket, client, bodyshop.id, search, currentUser.email]); - - const handleSetConsent = async (phone_number, consent_status) => { - try { - const response = await axios.post("/sms/setConsent", { - bodyshopid: bodyshop.id, - phone_number, - consent_status, - reason: "Manual override in app", - changed_by: currentUser.email - }); - - const updatedConsent = { - ...response.data.consent, - phone_number_consent_history: response.data.consent.phone_number_consent_history.map((history) => ({ - ...history, - __typename: "phone_number_consent_history" - })) - }; - - // Update Apollo cache - const cacheData = client.readQuery({ - query: GET_PHONE_NUMBER_CONSENTS, - variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } - }); - - let cacheUpdated = false; - if (cacheData?.phone_number_consent) { - const isPhoneNumberInCache = cacheData.phone_number_consent.some( - (consent) => consent.phone_number === phone_number - ); - - const updatedConsents = isPhoneNumberInCache - ? cacheData.phone_number_consent.map((consent) => - consent.phone_number === phone_number ? updatedConsent : consent - ) - : [...cacheData.phone_number_consent, updatedConsent]; - - cacheUpdated = client.writeQuery( - { - query: GET_PHONE_NUMBER_CONSENTS, - variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } - }, - { - phone_number_consent: updatedConsents - } - ); - - console.log("Cache update in handleSetConsent:", { - phone_number, - consent_status, - updatedConsents, - search - }); - } else { - console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in handleSetConsent"); - } - - // Always refetch to ensure UI updates - await refetch(); - - notification.success({ - message: t("consent.update_success") - }); - } catch (error) { - notification.error({ - message: t("consent.update_failed") - }); - console.error("Error updating consent:", error.message, error.stack); - } - }; - - const handleBulkUpload = async (file) => { - const reader = new FileReader(); - reader.onload = async (e) => { - const text = e.target.result; - const lines = text.split("\n").slice(1); // Skip header - const consents = lines - .filter((line) => line.trim()) - .map((line) => { - const [phone_number, consent_status] = line.split(","); - return { - phone_number: phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""), - consent_status: consent_status.trim().toLowerCase() === "true" - }; - }); - - try { - const response = await axios.post("/sms/bulkSetConsent", { - bodyshopid: bodyshop.id, - consents - }); - - const updatedConsents = response.data.consents.map((consent) => ({ - ...consent, - phone_number_consent_history: consent.phone_number_consent_history.map((history) => ({ - ...history, - __typename: "phone_number_consent_history" - })) - })); - - // Update Apollo cache - const cacheData = client.readQuery({ - query: GET_PHONE_NUMBER_CONSENTS, - variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } - }); - - if (cacheData?.phone_number_consent) { - const updatedConsentsMap = new Map(updatedConsents.map((consent) => [consent.phone_number, consent])); - - const mergedConsents = cacheData.phone_number_consent.map((consent) => - updatedConsentsMap.has(consent.phone_number) ? updatedConsentsMap.get(consent.phone_number) : consent - ); - - updatedConsents.forEach((consent) => { - if (!mergedConsents.some((c) => c.phone_number === consent.phone_number)) { - mergedConsents.push(consent); - } - }); - - client.writeQuery( - { - query: GET_PHONE_NUMBER_CONSENTS, - variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined } - }, - { - phone_number_consent: mergedConsents - } - ); - - console.log("Cache update in handleBulkUpload:", { updatedConsents, mergedConsents }); - } else { - console.warn("No cached data for GET_PHONE_NUMBER_CONSENTS in handleBulkUpload"); - } - - // Refetch to ensure UI updates - await refetch(); - } catch (error) { - notification.error({ - message: t("consent.bulk_update_failed") - }); - console.error("Bulk upload failed:", error.message, error.stack); - } - }; - reader.readAsText(file); - return false; - }; - - if (!bodyshop?.enforce_sms_consent) return null; - const columns = [ { title: t("consent.phone_number"), @@ -249,15 +37,6 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) { render: (text) => {text}, sorter: (a, b) => a.phone_number.localeCompare(b.phone_number) }, - { - title: t("consent.status"), - dataIndex: "consent_status", - render: (status, record) => ( - - handleSetConsent(record.phone_number, checked)} /> - - ) - }, { title: t("consent.updated_at"), dataIndex: "consent_updated_at", @@ -272,9 +51,7 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) { onSearch={(value) => setSearch(value)} style={{ marginBottom: 16 }} /> - - - +
({ - updateBodyshopEnforceConsent: (enforce_sms_consent) => dispatch(updateBodyshopEnforceConsent(enforce_sms_consent)) -}); +const mapDispatchToProps = (dispatch) => ({}); -function ShopInfoConsentComponent({ bodyshop, updateBodyshopEnforceConsent }) { +function ShopInfoConsentComponent({ bodyshop }) { const { t } = useTranslation(); - const [updateEnforceConsent] = useMutation(UPDATE_BODYSHOP_ENFORCE_CONSENT, { - onError: (error) => { - message.error(t("settings.enforce_sms_consent_error")); - console.error("Error updating enforce_sms_consent:", error); - }, - onCompleted: (data) => { - message.success(t("settings.enforce_sms_consent_success")); - updateBodyshopEnforceConsent(data.update_bodyshops_by_pk.enforce_sms_consent); - } - }); - - const enforceConsent = bodyshop?.enforce_sms_consent ?? false; - return (
{t("settings.title")} -
- {t("settings.enforce_sms_consent")} - - { - if (!checked && enforceConsent) return; // Prevent disabling - updateEnforceConsent({ - variables: { id: bodyshop.id, enforce_sms_consent: checked }, - optimisticResponse: { - update_bodyshops_by_pk: { - __typename: "bodyshops", - id: bodyshop.id, - enforce_sms_consent: checked - } - } - }); - }} - disabled={enforceConsent} - /> - -
- {enforceConsent && } + {}
); } diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index af16899cf..7faff13a2 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -142,7 +142,6 @@ export const QUERY_BODYSHOP = gql` intellipay_config md_ro_guard notification_followers - enforce_sms_consent employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) { id name @@ -364,12 +363,3 @@ export const GET_ACTIVE_EMPLOYEES_IN_SHOP = gql` } } `; - -export const UPDATE_BODYSHOP_ENFORCE_CONSENT = gql` - mutation UPDATE_BODYSHOP_ENFORCE_CONSENT($id: uuid!, $enforce_sms_consent: Boolean!) { - update_bodyshops_by_pk(pk_columns: { id: $id }, _set: { enforce_sms_consent: $enforce_sms_consent }) { - id - enforce_sms_consent - } - } -`; diff --git a/client/src/redux/user/user.actions.js b/client/src/redux/user/user.actions.js index 125aab415..01ba22534 100644 --- a/client/src/redux/user/user.actions.js +++ b/client/src/redux/user/user.actions.js @@ -123,8 +123,3 @@ export const setImexShopId = (imexshopid) => ({ type: UserActionTypes.SET_IMEX_SHOP_ID, payload: imexshopid }); - -export const updateBodyshopEnforceConsent = (enforce_sms_consent) => ({ - type: UserActionTypes.UPDATE_BODYSHOP_ENFORCE_CONSENT, - payload: enforce_sms_consent -}); diff --git a/client/src/redux/user/user.reducer.js b/client/src/redux/user/user.reducer.js index a72d4f068..eebb32433 100644 --- a/client/src/redux/user/user.reducer.js +++ b/client/src/redux/user/user.reducer.js @@ -125,14 +125,7 @@ const userReducer = (state = INITIAL_STATE, action) => { ...state, imexshopid: action.payload }; - case UserActionTypes.UPDATE_BODYSHOP_ENFORCE_CONSENT: - return { - ...state, - bodyshop: { - ...state.bodyshop, - enforce_sms_consent: action.payload - } - }; + default: return state; } diff --git a/client/src/redux/user/user.types.js b/client/src/redux/user/user.types.js index ff21dbb5a..d9cd6fe62 100644 --- a/client/src/redux/user/user.types.js +++ b/client/src/redux/user/user.types.js @@ -33,7 +33,6 @@ const UserActionTypes = { CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE", SET_CURRENT_EULA: "SET_CURRENT_EULA", EULA_ACCEPTED: "EULA_ACCEPTED", - SET_IMEX_SHOP_ID: "SET_IMEX_SHOP_ID", - UPDATE_BODYSHOP_ENFORCE_CONSENT: "UPDATE_BODYSHOP_ENFORCE_CONSENT" + SET_IMEX_SHOP_ID: "SET_IMEX_SHOP_ID" }; export default UserActionTypes; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index e753871c4..5c4e9cd85 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -656,6 +656,7 @@ } }, "labels": { + "consent_settings": "Consent Settings", "2tiername": "Name => RO", "2tiersetup": "2 Tier Setup", "2tiersource": "Source => RO", @@ -2377,7 +2378,8 @@ "errors": { "invalidphone": "The phone number is invalid. Unable to open conversation. ", "noattachedjobs": "No Jobs have been associated to this conversation. ", - "updatinglabel": "Error updating label. {{error}}" + "updatinglabel": "Error updating label. {{error}}", + "no_consent": "This phone number has not consented to receive messages." }, "labels": { "addlabel": "Add a label to this conversation.", @@ -2393,7 +2395,8 @@ "selectmedia": "Select Media", "sentby": "Sent by {{by}} at {{time}}", "typeamessage": "Send a message...", - "unarchive": "Unarchive" + "unarchive": "Unarchive", + "no_consent": "No Consent" }, "render": { "conversation_list": "Conversation List" @@ -3862,6 +3865,14 @@ "validation": { "unique_vendor_name": "You must enter a unique vendor name." } + }, + "consent": { + "phone_number": "Phone Number", + "status": "Consent Status", + "updated_at": "Last Updated" + }, + "settings": { + "title": "Phone Number Opt-Out list" } } } diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index fd83ecbc4..025166032 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -957,7 +957,6 @@ - enforce_conversion_category - enforce_conversion_csr - enforce_referral - - enforce_sms_consent - entegral_configuration - entegral_id - features @@ -1068,7 +1067,6 @@ - enforce_conversion_category - enforce_conversion_csr - enforce_referral - - enforce_sms_consent - federal_tax_id - id - inhousevendorid @@ -5864,104 +5862,12 @@ url: '{{$base_url}}/opensearch' version: 2 - table: - name: phone_number_consent + name: phone_number_opt_out schema: public object_relationships: - name: bodyshop using: foreign_key_constraint_on: bodyshopid - array_relationships: - - name: phone_number_consent_history - using: - foreign_key_constraint_on: - column: phone_number_consent_id - table: - name: phone_number_consent_history - schema: public - insert_permissions: - - role: user - permission: - check: - bodyshop: - associations: - _and: - - user: - authid: - _eq: X-Hasura-User-Id - - active: - _eq: true - columns: - - consent_status - - phone_number - - consent_updated_at - - created_at - - updated_at - - bodyshopid - - id - comment: "" - select_permissions: - - role: user - permission: - columns: - - consent_status - - phone_number - - consent_updated_at - - created_at - - updated_at - - bodyshopid - - id - filter: - bodyshop: - associations: - _and: - - user: - authid: - _eq: X-Hasura-User-Id - - active: - _eq: true - comment: "" - update_permissions: - - role: user - permission: - columns: - - bodyshopid - - consent_status - - consent_updated_at - - created_at - - phone_number - - updated_at - filter: {} - check: null - comment: "" -- table: - name: phone_number_consent_history - schema: public - object_relationships: - - name: phone_number_consent - using: - foreign_key_constraint_on: phone_number_consent_id - select_permissions: - - role: user - permission: - columns: - - new_value - - old_value - - changed_by - - reason - - changed_at - - id - - phone_number_consent_id - filter: - phone_number_consent: - bodyshop: - associations: - _and: - - user: - authid: - _eq: X-Hasura-User-Id - - active: - _eq: true - comment: "" - table: name: phonebook schema: public diff --git a/hasura/migrations/1747850002182_drop_table_public_phone_number_consent_history/down.sql b/hasura/migrations/1747850002182_drop_table_public_phone_number_consent_history/down.sql new file mode 100644 index 000000000..8b7230c77 --- /dev/null +++ b/hasura/migrations/1747850002182_drop_table_public_phone_number_consent_history/down.sql @@ -0,0 +1,3 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- DROP table "public"."phone_number_consent_history"; diff --git a/hasura/migrations/1747850002182_drop_table_public_phone_number_consent_history/up.sql b/hasura/migrations/1747850002182_drop_table_public_phone_number_consent_history/up.sql new file mode 100644 index 000000000..1093c487f --- /dev/null +++ b/hasura/migrations/1747850002182_drop_table_public_phone_number_consent_history/up.sql @@ -0,0 +1 @@ +DROP table "public"."phone_number_consent_history"; diff --git a/hasura/migrations/1747850205206_alter_table_public_phone_number_consent_drop_column_consent_status/down.sql b/hasura/migrations/1747850205206_alter_table_public_phone_number_consent_drop_column_consent_status/down.sql new file mode 100644 index 000000000..c7f137e6f --- /dev/null +++ b/hasura/migrations/1747850205206_alter_table_public_phone_number_consent_drop_column_consent_status/down.sql @@ -0,0 +1,2 @@ +alter table "public"."phone_number_consent" alter column "consent_status" drop not null; +alter table "public"."phone_number_consent" add column "consent_status" bool; diff --git a/hasura/migrations/1747850205206_alter_table_public_phone_number_consent_drop_column_consent_status/up.sql b/hasura/migrations/1747850205206_alter_table_public_phone_number_consent_drop_column_consent_status/up.sql new file mode 100644 index 000000000..8daf3ed0d --- /dev/null +++ b/hasura/migrations/1747850205206_alter_table_public_phone_number_consent_drop_column_consent_status/up.sql @@ -0,0 +1 @@ +alter table "public"."phone_number_consent" drop column "consent_status" cascade; diff --git a/hasura/migrations/1747850221535_alter_table_public_phone_number_consent_drop_column_consent_updated_at/down.sql b/hasura/migrations/1747850221535_alter_table_public_phone_number_consent_drop_column_consent_updated_at/down.sql new file mode 100644 index 000000000..74f22f6fb --- /dev/null +++ b/hasura/migrations/1747850221535_alter_table_public_phone_number_consent_drop_column_consent_updated_at/down.sql @@ -0,0 +1,3 @@ +alter table "public"."phone_number_consent" alter column "consent_updated_at" set default now(); +alter table "public"."phone_number_consent" alter column "consent_updated_at" drop not null; +alter table "public"."phone_number_consent" add column "consent_updated_at" timestamptz; diff --git a/hasura/migrations/1747850221535_alter_table_public_phone_number_consent_drop_column_consent_updated_at/up.sql b/hasura/migrations/1747850221535_alter_table_public_phone_number_consent_drop_column_consent_updated_at/up.sql new file mode 100644 index 000000000..154b0a1e6 --- /dev/null +++ b/hasura/migrations/1747850221535_alter_table_public_phone_number_consent_drop_column_consent_updated_at/up.sql @@ -0,0 +1 @@ +alter table "public"."phone_number_consent" drop column "consent_updated_at" cascade; diff --git a/hasura/migrations/1747850386584_rename_table_public_phone_number_consent/down.sql b/hasura/migrations/1747850386584_rename_table_public_phone_number_consent/down.sql new file mode 100644 index 000000000..ede0fa6ca --- /dev/null +++ b/hasura/migrations/1747850386584_rename_table_public_phone_number_consent/down.sql @@ -0,0 +1 @@ +alter table "public"."phone_number_opt_out" rename to "phone_number_consent"; diff --git a/hasura/migrations/1747850386584_rename_table_public_phone_number_consent/up.sql b/hasura/migrations/1747850386584_rename_table_public_phone_number_consent/up.sql new file mode 100644 index 000000000..592a8c243 --- /dev/null +++ b/hasura/migrations/1747850386584_rename_table_public_phone_number_consent/up.sql @@ -0,0 +1 @@ +alter table "public"."phone_number_consent" rename to "phone_number_opt_out"; diff --git a/hasura/migrations/1747850458206_alter_table_public_bodyshops_drop_column_enforce_sms_consent/down.sql b/hasura/migrations/1747850458206_alter_table_public_bodyshops_drop_column_enforce_sms_consent/down.sql new file mode 100644 index 000000000..f25ada416 --- /dev/null +++ b/hasura/migrations/1747850458206_alter_table_public_bodyshops_drop_column_enforce_sms_consent/down.sql @@ -0,0 +1,2 @@ +alter table "public"."bodyshops" alter column "enforce_sms_consent" drop not null; +alter table "public"."bodyshops" add column "enforce_sms_consent" bool; diff --git a/hasura/migrations/1747850458206_alter_table_public_bodyshops_drop_column_enforce_sms_consent/up.sql b/hasura/migrations/1747850458206_alter_table_public_bodyshops_drop_column_enforce_sms_consent/up.sql new file mode 100644 index 000000000..57b8d6ebd --- /dev/null +++ b/hasura/migrations/1747850458206_alter_table_public_bodyshops_drop_column_enforce_sms_consent/up.sql @@ -0,0 +1 @@ +alter table "public"."bodyshops" drop column "enforce_sms_consent" cascade; diff --git a/package-lock.json b/package-lock.json index 8e0440f9d..0ee5c9545 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "aws4": "^1.13.2", "axios": "^1.8.4", "better-queue": "^3.8.12", - "bullmq": "^5.52.3", + "bullmq": "^5.53.0", "chart.js": "^4.4.8", "cloudinary": "^2.6.1", "compression": "^1.8.0", @@ -4634,9 +4634,9 @@ } }, "node_modules/bullmq": { - "version": "5.52.3", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.52.3.tgz", - "integrity": "sha512-UaVkg+uSgylYWjD6/d8TVm87SjDVZ5jKwDVh/pJACmStn71aIzOIpGazh2JrkGISgT10Q/lG2I40FhPg0KgNCQ==", + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.53.0.tgz", + "integrity": "sha512-AbzcwR+9GdgrenolOC9kApF+TkUKZpUCMiFbXgRYw9ivWhOfLCqKeajIptM7NdwhY7cpXgv+QpbweUuQZUxkyA==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", diff --git a/package.json b/package.json index a2da483f7..5eec68f9b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "aws4": "^1.13.2", "axios": "^1.8.4", "better-queue": "^3.8.12", - "bullmq": "^5.52.3", + "bullmq": "^5.53.0", "chart.js": "^4.4.8", "cloudinary": "^2.6.1", "compression": "^1.8.0", diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 84e20db6f..d61d814b9 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2805,7 +2805,6 @@ exports.GET_BODYSHOP_BY_ID = ` intellipay_config state notification_followers - enforce_sms_consent } } `; @@ -2969,132 +2968,3 @@ exports.GET_JOB_WATCHERS_MINIMAL = ` } } `; - -// Query to get consent status for a single phone number -exports.GET_PHONE_NUMBER_CONSENT = ` - query GET_PHONE_NUMBER_CONSENT($bodyshopid: uuid!, $phone_number: String!) { - phone_number_consent(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) { - id - bodyshopid - phone_number - consent_status - created_at - updated_at - consent_updated_at - phone_number_consent_history(order_by: { changed_at: desc }) { - id - old_value - new_value - reason - changed_at - changed_by - } - } - } -`; - -// Query to get consent statuses for multiple phone numbers -exports.GET_PHONE_NUMBER_CONSENTS = ` - query GET_PHONE_NUMBER_CONSENTS($bodyshopid: uuid!, $phone_numbers: [String!]) { - phone_number_consent(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _in: $phone_numbers } }) { - id - bodyshopid - phone_number - consent_status - created_at - updated_at - consent_updated_at - phone_number_consent_history(order_by: { changed_at: desc }) { - id - old_value - new_value - reason - changed_at - changed_by - } - } - } -`; - -// Mutation to update enforce_sms_consent -exports.UPDATE_BODYSHOP_ENFORCE_CONSENT = ` - mutation UPDATE_BODYSHOP_ENFORCE_CONSENT($id: uuid!, $enforce_sms_consent: Boolean!) { - update_bodyshops_by_pk( - pk_columns: { id: $id } - _set: { enforce_sms_consent: $enforce_sms_consent } - ) { - id - enforce_sms_consent - } - } -`; - -// Mutation to set consent status for a single phone number -exports.SET_PHONE_NUMBER_CONSENT = ` - mutation SET_PHONE_NUMBER_CONSENT($bodyshopid: uuid!, $phone_number: String!, $consent_status: Boolean!) { - insert_phone_number_consent_one( - object: { - bodyshopid: $bodyshopid - phone_number: $phone_number - consent_status: $consent_status - consent_updated_at: "now()" - } - on_conflict: { - constraint: phone_number_consent_bodyshopid_phone_number_key - update_columns: [consent_status, consent_updated_at] - } - ) { - id - bodyshopid - phone_number - consent_status - created_at - updated_at - consent_updated_at - } - } -`; - -// Mutation to set consent status for multiple phone numbers -exports.BULK_SET_PHONE_NUMBER_CONSENT = ` - mutation BULK_SET_PHONE_NUMBER_CONSENT($objects: [phone_number_consent_insert_input!]!) { - insert_phone_number_consent( - objects: $objects - on_conflict: { - constraint: phone_number_consent_bodyshopid_phone_number_key - update_columns: [consent_status, consent_updated_at] - } - ) { - affected_rows - returning { - id - bodyshopid - phone_number - consent_status - created_at - updated_at - consent_updated_at - } - } - } -`; - -// Mutation to insert multiple consent history records -exports.INSERT_PHONE_NUMBER_CONSENT_HISTORY = ` - mutation INSERT_PHONE_NUMBER_CONSENT_HISTORY($objects: [phone_number_consent_history_insert_input!]!) { - insert_phone_number_consent_history( - objects: $objects - ) { - affected_rows - returning { - id - phone_number_consent_id - old_value - new_value - reason - changed_at - changed_by - } - } - } -`; diff --git a/server/routes/smsRoutes.js b/server/routes/smsRoutes.js index 917699aaf..bb23d24e8 100644 --- a/server/routes/smsRoutes.js +++ b/server/routes/smsRoutes.js @@ -5,7 +5,6 @@ const { receive } = require("../sms/receive"); const { send } = require("../sms/send"); const { status, markConversationRead } = require("../sms/status"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -const { setConsent, bulkSetConsent } = require("../sms/consent"); // Twilio Webhook Middleware for production const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }); @@ -14,7 +13,5 @@ router.post("/receive", twilioWebhookMiddleware, receive); router.post("/send", validateFirebaseIdTokenMiddleware, send); router.post("/status", twilioWebhookMiddleware, status); router.post("/markConversationRead", validateFirebaseIdTokenMiddleware, markConversationRead); -router.post("/setConsent", validateFirebaseIdTokenMiddleware, setConsent); -router.post("/bulkSetConsent", validateFirebaseIdTokenMiddleware, bulkSetConsent); module.exports = router; diff --git a/server/sms/consent.js b/server/sms/consent.js deleted file mode 100644 index d7a997423..000000000 --- a/server/sms/consent.js +++ /dev/null @@ -1,215 +0,0 @@ -const { - SET_PHONE_NUMBER_CONSENT, - BULK_SET_PHONE_NUMBER_CONSENT, - INSERT_PHONE_NUMBER_CONSENT_HISTORY -} = require("../graphql-client/queries"); -const { phone } = require("phone"); -const gqlClient = require("../graphql-client/graphql-client").client; - -/** - * Set SMS consent for a phone number - * @param req - * @param res - * @returns {Promise<*>} - */ -const setConsent = async (req, res) => { - const { bodyshopid, phone_number, consent_status, reason, changed_by } = req.body; - const { - logger, - ioRedis, - ioHelpers: { getBodyshopRoom }, - sessionUtils: { getBodyshopFromRedis } - } = req; - - if (!bodyshopid || !phone_number || consent_status === undefined || !reason || !changed_by) { - logger.log("set-consent-error", "ERROR", req.user.email, null, { - type: "missing-parameters", - bodyshopid, - phone_number, - consent_status, - reason, - changed_by - }); - return res.status(400).json({ success: false, message: "Missing required parameter(s)." }); - } - - try { - // Check enforce_sms_consent - const bodyShopData = await getBodyshopFromRedis(bodyshopid); - const enforceConsent = bodyShopData?.enforce_sms_consent ?? false; - - if (!enforceConsent) { - logger.log("set-consent-error", "ERROR", req.user.email, null, { - type: "consent-not-enforced", - bodyshopid - }); - return res.status(403).json({ success: false, message: "SMS consent enforcement is not enabled." }); - } - - const normalizedPhone = phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""); - const consentResponse = await gqlClient.request(SET_PHONE_NUMBER_CONSENT, { - bodyshopid, - phone_number: normalizedPhone, - consent_status - }); - - const consent = consentResponse.insert_phone_number_consent_one; - - // Log audit history - const historyResponse = await gqlClient.request(INSERT_PHONE_NUMBER_CONSENT_HISTORY, { - objects: [ - { - phone_number_consent_id: consent.id, - old_value: null, // Not tracking old value - new_value: consent_status, - reason, - changed_by, - changed_at: "now()" - } - ] - }); - - const history = historyResponse.insert_phone_number_consent_history.returning[0]; - - // Emit WebSocket event - const broadcastRoom = getBodyshopRoom(bodyshopid); - ioRedis.to(broadcastRoom).emit("consent-changed", { - bodyshopId: bodyshopid, - phone_number: normalizedPhone, - consent_status, - reason - }); - - logger.log("set-consent-success", "DEBUG", req.user.email, null, { - bodyshopid, - phone_number: normalizedPhone, - consent_status - }); - - // Return both consent and history - res.status(200).json({ - success: true, - consent: { - ...consent, - phone_number_consent_history: [history] - } - }); - } catch (error) { - logger.log("set-consent-error", "ERROR", req.user.email, null, { - bodyshopid, - phone_number, - error: error.message, - stack: error.stack - }); - res.status(500).json({ success: false, message: "Failed to update consent status." }); - } -}; - -/** - * Bulk set SMS consent for multiple phone numbers - * @param req - * @param res - * @returns {Promise<*>} - */ -const bulkSetConsent = async (req, res) => { - const { bodyshopid, consents } = req.body; // consents: [{ phone_number, consent_status }] - const { - logger, - ioRedis, - ioHelpers: { getBodyshopRoom }, - sessionUtils: { getBodyshopFromRedis } - } = req; - - if (!bodyshopid || !Array.isArray(consents) || consents.length === 0) { - logger.log("bulk-set-consent-error", "ERROR", req.user.email, null, { - type: "missing-parameters", - bodyshopid, - consents - }); - return res.status(400).json({ success: false, message: "Missing or invalid parameters." }); - } - - try { - // Check enforce_sms_consent - const bodyShopData = await getBodyshopFromRedis(bodyshopid); - const enforceConsent = bodyShopData?.enforce_sms_consent ?? false; - - if (!enforceConsent) { - logger.log("bulk-set-consent-error", "ERROR", req.user.email, null, { - type: "consent-not-enforced", - bodyshopid - }); - return res.status(403).json({ success: false, message: "SMS consent enforcement is not enabled." }); - } - - const objects = consents.map(({ phone_number, consent_status }) => ({ - bodyshopid, - phone_number: phone(phone_number, "CA").phoneNumber.replace(/^\+1/, ""), - consent_status, - consent_updated_at: "now()" - })); - - // Insert or update phone_number_consent records - const consentResponse = await gqlClient.request(BULK_SET_PHONE_NUMBER_CONSENT, { - objects - }); - - const updatedConsents = consentResponse.insert_phone_number_consent.returning; - - // Log audit history - const historyObjects = updatedConsents.map((consent) => ({ - phone_number_consent_id: consent.id, - old_value: null, // Not tracking old value for bulk updates - new_value: consent.consent_status, - reason: "System update via bulk upload", - changed_by: "system", - changed_at: "now()" - })); - - const historyResponse = await gqlClient.request(INSERT_PHONE_NUMBER_CONSENT_HISTORY, { - objects: historyObjects - }); - - const history = historyResponse.insert_phone_number_consent_history.returning; - - // Combine consents with their history - const consentsWithhistory = updatedConsents.map((consent, index) => ({ - ...consent, - phone_number_consent_history: [history[index]] - })); - - // Emit WebSocket events for each consent change - const broadcastRoom = getBodyshopRoom(bodyshopid); - updatedConsents.forEach((consent) => { - ioRedis.to(broadcastRoom).emit("consent-changed", { - bodyshopId: bodyshopid, - phone_number: consent.phone_number, - consent_status: consent.consent_status, - reason: "System update via bulk upload" - }); - }); - - logger.log("bulk-set-consent-success", "DEBUG", req.user.email, null, { - bodyshopid, - updatedCount: updatedConsents.length - }); - - res.status(200).json({ - success: true, - updatedCount: updatedConsents.length, - consents: consentsWithhistory - }); - } catch (error) { - logger.log("bulk-set-consent-error", "ERROR", req.user.email, null, { - bodyshopid, - error: error.message, - stack: error.stack - }); - res.status(500).json({ success: false, message: "Failed to update consents." }); - } -}; - -module.exports = { - setConsent, - bulkSetConsent -}; diff --git a/server/sms/receive.js b/server/sms/receive.js index 59c4b1b32..ad539dbe4 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -3,8 +3,7 @@ const { FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, UNARCHIVE_CONVERSATION, CREATE_CONVERSATION, - INSERT_MESSAGE, - SET_PHONE_NUMBER_CONSENT + INSERT_MESSAGE } = require("../graphql-client/queries"); const { phone } = require("phone"); const { admin } = require("../firebase/firebase-handler"); @@ -17,11 +16,11 @@ const InstanceManager = require("../utils/instanceMgr").default; * @returns {Promise<*>} */ const receive = async (req, res) => { + console.dir(req.body); const { logger, ioRedis, - ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }, - sessionUtils: { getBodyshopFromRedis } + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } } = req; const loggerData = { @@ -54,35 +53,6 @@ const receive = async (req, res) => { const bodyshop = response.bodyshops[0]; - // Step 2: Check enforce_sms_consent - const bodyShopData = await getBodyshopFromRedis(bodyshopid); - const enforceConsent = bodyShopData?.enforce_sms_consent ?? false; - - // Step 3: Handle consent only if enforce_sms_consent is true - if (enforceConsent) { - const normalizedPhone = phone(req.body.From, "CA").phoneNumber.replace(/^\+1/, ""); - const isStop = req.body.Body.toUpperCase().includes("STOP"); - const consentStatus = isStop ? false : true; - const reason = isStop ? "Customer texted STOP" : "Inbound message received"; - - const consentResponse = await client.request(SET_PHONE_NUMBER_CONSENT, { - bodyshopid: bodyshop.id, - phone_number: normalizedPhone, - consent_status: consentStatus, - reason, - changed_by: "system" - }); - - // Emit WebSocket event for consent change - const broadcastRoom = getBodyshopRoom(bodyshop.id); - ioRedis.to(broadcastRoom).emit("consent-changed", { - bodyshopId: bodyshop.id, - phone_number: normalizedPhone, - consent_status: consentStatus, - reason - }); - } - // Step 4: Process conversation const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); const existingConversation = sortedConversations.length diff --git a/server/sms/send.js b/server/sms/send.js index 81b2d4d6d..aa3c5a84c 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -1,6 +1,6 @@ const twilio = require("twilio"); const { phone } = require("phone"); -const { INSERT_MESSAGE, GET_PHONE_NUMBER_CONSENT } = require("../graphql-client/queries"); +const { INSERT_MESSAGE } = require("../graphql-client/queries"); const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); const gqlClient = require("../graphql-client/graphql-client").client; @@ -42,30 +42,6 @@ const send = async (req, res) => { } try { - // Check bodyshop's enforce_sms_consent setting - const bodyShopData = await getBodyshopFromRedis(bodyshopid); - const enforceConsent = bodyShopData?.enforce_sms_consent ?? false; - - // Check consent only if enforcement is enabled - if (enforceConsent) { - const normalizedPhone = phone(to, "CA").phoneNumber.replace(/^\+1/, ""); - const consentResponse = await gqlClient.request(GET_PHONE_NUMBER_CONSENT, { - bodyshopid, - phone_number: normalizedPhone - }); - if (!consentResponse.phone_number_consent?.length || !consentResponse.phone_number_consent[0].consent_status) { - logger.log("sms-outbound-error", "ERROR", req.user.email, null, { - type: "no-consent", - phone_number: normalizedPhone, - conversationid - }); - return res.status(403).json({ - success: false, - message: "Phone number has not consented to messaging." - }); - } - } - const message = await client.messages.create({ body, messagingServiceSid, diff --git a/server/sms/status.js b/server/sms/status.js index 509c76d6b..385dbaa40 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -9,6 +9,7 @@ const logger = require("../utils/logger"); * @returns {Promise<*>} */ const status = async (req, res) => { + console.dir(req.body); const { SmsSid, SmsStatus } = req.body; const { ioRedis, From 8c8c68867dc603b0ff7964ec635307ae1395c945 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 May 2025 14:39:17 -0400 Subject: [PATCH 17/22] feature/IO-3182-Phone-Number-Consent - Checkpoint --- server/routes/smsRoutes.js | 1 + server/sms/receive.js | 2 +- server/sms/send.js | 4 ++-- server/sms/status.js | 1 - 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/routes/smsRoutes.js b/server/routes/smsRoutes.js index bb23d24e8..c09cc1632 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: This is never actually doing anything, we should probably verify const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }); router.post("/receive", twilioWebhookMiddleware, receive); diff --git a/server/sms/receive.js b/server/sms/receive.js index ad539dbe4..bf5262b25 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -16,7 +16,6 @@ const InstanceManager = require("../utils/instanceMgr").default; * @returns {Promise<*>} */ const receive = async (req, res) => { - console.dir(req.body); const { logger, ioRedis, @@ -118,6 +117,7 @@ const receive = async (req, res) => { }; const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id); + ioRedis.to(broadcastRoom).emit("new-message-summary", { ...commonPayload, existingConversation: !!existingConversation, diff --git a/server/sms/send.js b/server/sms/send.js index aa3c5a84c..c5e897a98 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -5,7 +5,7 @@ const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY const gqlClient = require("../graphql-client/graphql-client").client; const send = async (req, res) => { - const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid, bodyshopid } = req.body; + const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body; const { ioRedis, logger, @@ -25,7 +25,7 @@ const send = async (req, res) => { image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] }); - if (!to || !messagingServiceSid || (!body && selectedMedia.length === 0) || !conversationid || !bodyshopid) { + if (!to || !messagingServiceSid || (!body && selectedMedia.length === 0) || !conversationid) { logger.log("sms-outbound-error", "ERROR", req.user.email, null, { type: "missing-parameters", messagingServiceSid, diff --git a/server/sms/status.js b/server/sms/status.js index 385dbaa40..509c76d6b 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -9,7 +9,6 @@ const logger = require("../utils/logger"); * @returns {Promise<*>} */ const status = async (req, res) => { - console.dir(req.body); const { SmsSid, SmsStatus } = req.body; const { ioRedis, From 6afa50332b8be3ebc3a1b10568cdbc25e1efbe13 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 May 2025 15:03:02 -0400 Subject: [PATCH 18/22] feature/IO-3182-Phone-Number-Consent - Checkpoint --- .../chat-conversation-list.component.jsx | 83 +++++++++++++++---- .../chat-send-message.component.jsx | 16 ++-- .../phone-number-consent.component.jsx | 5 +- client/src/graphql/consent.queries.js | 48 ----------- .../graphql/phone-number-opt-out.queries.js | 28 +++++++ client/src/translations/en_us/common.json | 4 +- server/sms/send.js | 9 +- 7 files changed, 114 insertions(+), 79 deletions(-) delete mode 100644 client/src/graphql/consent.queries.js create mode 100644 client/src/graphql/phone-number-opt-out.queries.js diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index 16d4c0bf1..70bfc5b5d 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -1,5 +1,5 @@ import { Badge, Card, List, Space, Tag } from "antd"; -import React, { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { connect } from "react-redux"; import { Virtuoso } from "react-virtuoso"; import { createStructuredSelector } from "reselect"; @@ -10,35 +10,63 @@ import PhoneFormatter from "../../utils/PhoneFormatter"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import _ from "lodash"; import "./chat-conversation-list.styles.scss"; +import { useQuery } from "@apollo/client"; +import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries.js"; +import { phone } from "phone"; +import { useTranslation } from "react-i18next"; +import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - selectedConversation: selectSelectedConversation + selectedConversation: selectSelectedConversation, + bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId)) }); -function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) { - // That comma is there for a reason, do not remove it +function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) { + const { t } = useTranslation(); const [, forceUpdate] = useState(false); - // Re-render every minute + const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "")); + + const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, { + variables: { + bodyshopid: bodyshop.id, + phone_numbers: phoneNumbers + }, + skip: !conversationList.length, + fetchPolicy: "cache-and-network" + }); + + const optOutMap = useMemo(() => { + const map = new Map(); + optOutData?.phone_number_opt_out?.forEach((optOut) => { + map.set(optOut.phone_number, true); + }); + return map; + }, [optOutData?.phone_number_opt_out]); + useEffect(() => { const interval = setInterval(() => { - forceUpdate((prev) => !prev); // Toggle state to trigger re-render - }, 60000); // 1 minute in milliseconds - - return () => clearInterval(interval); // Cleanup on unmount + forceUpdate((prev) => !prev); + }, 60000); + return () => clearInterval(interval); }, []); - // Memoize the sorted conversation list - const sortedConversationList = React.useMemo(() => { + const sortedConversationList = useMemo(() => { return _.orderBy(conversationList, ["updated_at"], ["desc"]); }, [conversationList]); - const renderConversation = (index) => { + const renderConversation = (index, t) => { const item = sortedConversationList[index]; + const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); + // Check if the phone number exists in the consentMap + const hasOptOutEntry = optOutMap.has(normalizedPhone); + // Only consider it non-consented if it exists and consent_status is false + const isOptedOut = hasOptOutEntry ? optOutMap.get(normalizedPhone) : true; + const cardContentRight = {item.updated_at}; const cardContentLeft = item.job_conversations.length > 0 @@ -60,7 +88,12 @@ function ChatConversationListComponent({ conversationList, selectedConversation, ); - const cardExtra = ; + const cardExtra = ( + <> + + {hasOptOutEntry && !isOptedOut && {t("messaging.labels.no_consent")}} + + ); const getCardStyle = () => item.id === selectedConversation @@ -73,9 +106,25 @@ function ChatConversationListComponent({ conversationList, selectedConversation, onClick={() => setSelectedConversation(item.id)} className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`} > - -
{cardContentLeft}
-
{cardContentRight}
+ +
+ {cardContentLeft} +
+
+ {cardContentRight} +
); @@ -85,7 +134,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
renderConversation(index)} + itemContent={(index) => renderConversation(index, t)} style={{ height: "100%", width: "100%" }} />
diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index aa5b3f035..798532a3e 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -11,8 +11,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component"; import ChatPresetsComponent from "../chat-presets/chat-presets.component"; import { useQuery } from "@apollo/client"; -import { GET_PHONE_NUMBER_CONSENT } from "../../graphql/consent.queries"; import { phone } from "phone"; +import { GET_PHONE_NUMBER_OPT_OUT } from "../../graphql/phone-number-opt-out.queries"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -31,12 +31,12 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi const { t } = useTranslation(); const normalizedPhone = phone(conversation.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); - const { data: consentData } = useQuery(GET_PHONE_NUMBER_CONSENT, { + const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUT, { variables: { bodyshopid: bodyshop.id, phone_number: normalizedPhone }, fetchPolicy: "cache-and-network" }); - const isConsented = consentData?.phone_number_consent?.[0]?.consent_status ?? false; + const isOptedOut = !!optOutData?.phone_number_opt_out?.[0]; useEffect(() => { inputArea.current.focus(); @@ -45,7 +45,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi const handleEnter = () => { const selectedImages = selectedMedia.filter((i) => i.isSelected); if ((message === "" || !message) && selectedImages.length === 0) return; - if (!isConsented) return; + if (isOptedOut) return; // Prevent sending if phone number is opted out logImEXEvent("messaging_send_message"); if (selectedImages.length < 11) { @@ -69,7 +69,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi return (
- {!isConsented && } + {isOptedOut && } setMessage(e.target.value)} onPressEnter={(event) => { event.preventDefault(); - if (!event.shiftKey && isConsented) handleEnter(); + if (!event.shiftKey && !isOptedOut) handleEnter(); }} /> RO", "2tiersetup": "2 Tier Setup", "2tiersource": "Source => RO", @@ -3872,7 +3872,7 @@ "updated_at": "Last Updated" }, "settings": { - "title": "Phone Number Opt-Out list" + "title": "Phone Number Opt-Out List" } } } diff --git a/server/sms/send.js b/server/sms/send.js index c5e897a98..fdd2c81ae 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -4,13 +4,18 @@ const { INSERT_MESSAGE } = require("../graphql-client/queries"); const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); const gqlClient = require("../graphql-client/graphql-client").client; +/** + * Send an outbound SMS message + * @param req + * @param res + * @returns {Promise} + */ const send = async (req, res) => { const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body; const { ioRedis, logger, - ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }, - sessionUtils: { getBodyshopFromRedis } + ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } } = req; logger.log("sms-outbound", "DEBUG", req.user.email, null, { From 0541afceb8855511160fcc74944f3b42b4862f93 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 May 2025 15:17:11 -0400 Subject: [PATCH 19/22] feature/IO-3182-Phone-Number-Consent - Checkpoint --- .../chat-conversation-list.component.jsx | 5 +---- .../phone-number-consent.component.jsx | 9 +++++---- .../graphql/phone-number-opt-out.queries.js | 6 +++--- client/src/translations/en_us/common.json | 2 +- hasura/metadata/tables.yaml | 19 +++++++++++++++++++ 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index 70bfc5b5d..8bf185756 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -62,10 +62,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation, const renderConversation = (index, t) => { const item = sortedConversationList[index]; const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""); - // Check if the phone number exists in the consentMap const hasOptOutEntry = optOutMap.has(normalizedPhone); - // Only consider it non-consented if it exists and consent_status is false - const isOptedOut = hasOptOutEntry ? optOutMap.get(normalizedPhone) : true; const cardContentRight = {item.updated_at}; const cardContentLeft = @@ -91,7 +88,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation, const cardExtra = ( <> - {hasOptOutEntry && !isOptedOut && {t("messaging.labels.no_consent")}} + {hasOptOutEntry && {t("messaging.labels.no_consent")}} ); diff --git a/client/src/components/phone-number-consent/phone-number-consent.component.jsx b/client/src/components/phone-number-consent/phone-number-consent.component.jsx index fa0f810c0..020bb1750 100644 --- a/client/src/components/phone-number-consent/phone-number-consent.component.jsx +++ b/client/src/components/phone-number-consent/phone-number-consent.component.jsx @@ -39,9 +39,10 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) { sorter: (a, b) => a.phone_number.localeCompare(b.phone_number) }, { - title: t("consent.updated_at"), - dataIndex: "consent_updated_at", - render: (text) => {text} + title: t("consent.created_at"), + dataIndex: "created_at", + render: (text) => {text}, + sorter: (a, b) => new Date(a.created_at) - new Date(b.created_at) } ]; @@ -55,7 +56,7 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
Date: Wed, 21 May 2025 16:28:09 -0700 Subject: [PATCH 20/22] IO-3230 Customer List Excel Signed-off-by: Allan Carr --- .../report-center-modal.component.jsx | 8 +++++--- client/src/translations/en_us/common.json | 1 + client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + client/src/utils/TemplateConstants.js | 14 +++++++++++++- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx index ddea0aab2..336124eaa 100644 --- a/client/src/components/report-center-modal/report-center-modal.component.jsx +++ b/client/src/components/report-center-modal/report-center-modal.component.jsx @@ -6,6 +6,7 @@ import React, { 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 { QUERY_ACTIVE_EMPLOYEES, QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL } from "../../graphql/employees.queries"; import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries"; import { selectReportCenter } from "../../redux/modals/modals.selectors"; @@ -18,11 +19,10 @@ import EmployeeSearchSelectEmail from "../employee-search-select/employee-search import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; +import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filters-sorters-component"; import "./report-center-modal.styles.scss"; -import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component"; -import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; const mapStateToProps = createStructuredSelector({ reportCenterModal: selectReportCenter, @@ -389,5 +389,7 @@ const restrictedReports = [ { key: "job_costing_ro_date_detail", days: 183 }, { key: "job_costing_ro_estimator", days: 183 }, { key: "job_lifecycle_date_detail", days: 183 }, - { key: "job_lifecycle_date_summary", days: 183 } + { key: "job_lifecycle_date_summary", days: 183 }, + { key: "customer_list", days: 183 }, + { key: "customer_list_excel", days: 183 } ]; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index e753871c4..8ca3dadd3 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -3099,6 +3099,7 @@ "credits_not_received_date_vendorid": "Credits not Received by Vendor", "csi": "CSI Responses", "customer_list": "Customer List", + "customer_list_excel": "Customer List - Excel", "cycle_time_analysis": "Cycle Time Analysis", "estimates_written_converted": "Estimates Written/Converted", "estimator_detail": "Jobs by Estimator (Detail)", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index ad31f3cc6..909ce1936 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -3100,6 +3100,7 @@ "credits_not_received_date_vendorid": "", "csi": "", "customer_list": "", + "customer_list_excel": "", "cycle_time_analysis": "", "estimates_written_converted": "", "estimator_detail": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index e7ac5f8ac..68b8ad43e 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -3100,6 +3100,7 @@ "credits_not_received_date_vendorid": "", "csi": "", "customer_list": "", + "customer_list_excel": "", "cycle_time_analysis": "", "estimates_written_converted": "", "estimator_detail": "", diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index f9d53306c..867979a0c 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -2004,6 +2004,18 @@ export const TemplateList = (type, context) => { }, group: "customers" }, + customer_list_excel: { + title: i18n.t("reportcenter.templates.customer_list_excel"), + subject: i18n.t("reportcenter.templates.customer_list_excel"), + key: "customer_list_excel", + reporttype: "excel", + disabled: false, + rangeFilter: { + object: i18n.t("reportcenter.labels.objects.jobs"), + field: i18n.t("jobs.fields.date_invoiced") + }, + group: "customers" + }, exported_gsr_by_ro: { title: i18n.t("reportcenter.templates.exported_gsr_by_ro"), subject: i18n.t("reportcenter.templates.exported_gsr_by_ro"), @@ -2241,7 +2253,7 @@ export const TemplateList = (type, context) => { field: i18n.t("bills.fields.date") }, group: "purchases" - }, + } } : {}), ...(!type || type === "courtesycarcontract" From e3d854e02ba7b3e609b13ea917de37150af20952 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 21 May 2025 17:53:33 -0700 Subject: [PATCH 21/22] IO-3243 Job Costing TOW Signed-off-by: Allan Carr --- server/graphql-client/queries.js | 2 ++ server/job/job-costing.js | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index d61d814b9..386e21a9f 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -1596,6 +1596,7 @@ query QUERY_JOB_COSTING_DETAILS($id: uuid!) { ca_customer_gst dms_allocation cieca_pfl + cieca_stl materials joblines(where: { removed: { _eq: false } }) { id @@ -1712,6 +1713,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT ca_customer_gst dms_allocation cieca_pfl + cieca_stl materials joblines(where: {removed: {_eq: false}}) { id diff --git a/server/job/job-costing.js b/server/job/job-costing.js index 1210b92e9..29c9dbfce 100644 --- a/server/job/job-costing.js +++ b/server/job/job-costing.js @@ -567,6 +567,29 @@ function GenerateCostingData(job) { ); } + if (InstanceManager({ imex: false, rome: true })) { + const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_type === "OTTW"); + const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_type === "OTST"); + + if (!jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]]) + jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero(); + + jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = stlTowing + ? Dinero({ amount: Math.round(stlTowing.ttl_amt * 100) }) + : Dinero({ + amount: Math.round((job.towing_payable || 0) * 100) + }); + + if (!jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]]) + jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = Dinero(); + + jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = stlStorage + ? Dinero({ amount: Math.round(stlStorage.ttl_amt * 100) }) + : Dinero({ + amount: Math.round((job.storage_payable || 0) * 100) + }); + } + //Is it a DMS Setup? const selectedDmsAllocationConfig = (job.bodyshop.md_responsibility_centers.dms_defaults && From 5c47088b11c542c71e4c3c860f7c4cfdba361bd5 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 22 May 2025 11:37:47 -0400 Subject: [PATCH 22/22] release/2025-06-02 - Lint Updates --- .../phone-number-consent.component.jsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/client/src/components/phone-number-consent/phone-number-consent.component.jsx b/client/src/components/phone-number-consent/phone-number-consent.component.jsx index 020bb1750..7981a3db2 100644 --- a/client/src/components/phone-number-consent/phone-number-consent.component.jsx +++ b/client/src/components/phone-number-consent/phone-number-consent.component.jsx @@ -1,4 +1,4 @@ -import { useApolloClient, useQuery } from "@apollo/client"; +import { useQuery } from "@apollo/client"; import { Input, Table } from "antd"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -9,9 +9,6 @@ import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.qu import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import { TimeAgoFormatter } from "../../utils/DateFormatter"; -import { phone } from "phone"; -import { useSocket } from "../../contexts/SocketIO/useSocket.js"; -import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -23,13 +20,10 @@ const mapDispatchToProps = () => ({}); function PhoneNumberConsentList({ bodyshop, currentUser }) { const { t } = useTranslation(); const [search, setSearch] = useState(""); - const notification = useNotification(); - const { loading, data, refetch } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, { + const { loading, data } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, { variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }, fetchPolicy: "network-only" }); - const client = useApolloClient(); - const { socket } = useSocket(); const columns = [ {