feature/IO-3182-Phone-Number-Consent - Checkpoint

This commit is contained in:
Dave Richer
2025-05-20 16:04:36 -04:00
parent 9d81c68a4d
commit 83860152a9
12 changed files with 540 additions and 43 deletions

View File

@@ -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
}
}
}
`;

View File

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