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: {