feature/IO-3000-messaging-sockets-migrations2 -
- Checkpoint Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -1,35 +1,45 @@
|
|||||||
import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
||||||
import { gql } from "@apollo/client";
|
import { gql } from "@apollo/client";
|
||||||
|
|
||||||
|
const logLocal = (message, ...args) => {
|
||||||
|
if (import.meta.env.PROD) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`==================== ${message} ====================`);
|
||||||
|
console.dir({ ...args });
|
||||||
|
};
|
||||||
|
|
||||||
export const registerMessagingHandlers = ({ socket, client }) => {
|
export const registerMessagingHandlers = ({ socket, client }) => {
|
||||||
if (!(socket && client)) return;
|
if (!(socket && client)) return;
|
||||||
|
|
||||||
const handleNewMessageSummary = (message) => {
|
const handleNewMessageSummary = (message) => {
|
||||||
const { conversationId, newConversation, existingConversation, isoutbound } = message;
|
const { conversationId, newConversation, existingConversation, isoutbound } = message;
|
||||||
|
|
||||||
|
logLocal("handleNewMessageSummary", message);
|
||||||
|
|
||||||
if (!existingConversation && newConversation?.phone_num) {
|
if (!existingConversation && newConversation?.phone_num) {
|
||||||
const queryResults = client.cache.readQuery({
|
const queryResults = client.cache.readQuery({
|
||||||
query: CONVERSATION_LIST_QUERY,
|
query: CONVERSATION_LIST_QUERY,
|
||||||
variables: { offset: 0 }
|
variables: { offset: 0 }
|
||||||
});
|
});
|
||||||
|
|
||||||
const fullConversation = {
|
|
||||||
...newConversation,
|
|
||||||
updated_at: newConversation.updated_at || new Date().toISOString(),
|
|
||||||
unreadcnt: newConversation.unreadcnt || 0,
|
|
||||||
archived: newConversation.archived || false,
|
|
||||||
label: newConversation.label || null,
|
|
||||||
job_conversations: newConversation.job_conversations || [],
|
|
||||||
messages_aggregate: newConversation.messages_aggregate || {
|
|
||||||
aggregate: { count: isoutbound ? 0 : 1 }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
client.cache.writeQuery({
|
client.cache.writeQuery({
|
||||||
query: CONVERSATION_LIST_QUERY,
|
query: CONVERSATION_LIST_QUERY,
|
||||||
variables: { offset: 0 },
|
variables: { offset: 0 },
|
||||||
data: {
|
data: {
|
||||||
conversations: [fullConversation, ...(queryResults?.conversations || [])]
|
conversations: [
|
||||||
|
{
|
||||||
|
...newConversation,
|
||||||
|
updated_at: newConversation.updated_at || new Date().toISOString(),
|
||||||
|
unreadcnt: newConversation.unreadcnt || 0,
|
||||||
|
archived: newConversation.archived || false,
|
||||||
|
label: newConversation.label || null,
|
||||||
|
job_conversations: newConversation.job_conversations || [],
|
||||||
|
messages_aggregate: newConversation.messages_aggregate || {
|
||||||
|
aggregate: { count: isoutbound ? 0 : 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...(queryResults?.conversations || [])
|
||||||
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -51,9 +61,12 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNewMessageDetailed = (message) => {
|
const handleNewMessageDetailed = (message) => {
|
||||||
const { conversationId, newMessage } = message;
|
const { conversationId, newMessage } = message;
|
||||||
|
|
||||||
|
logLocal("handleNewMessageDetailed", message);
|
||||||
|
|
||||||
// Append the new message to the conversation's message list
|
// Append the new message to the conversation's message list
|
||||||
const queryResults = client.cache.readQuery({
|
const queryResults = client.cache.readQuery({
|
||||||
query: GET_CONVERSATION_DETAILS,
|
query: GET_CONVERSATION_DETAILS,
|
||||||
@@ -76,6 +89,10 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMessageChanged = (message) => {
|
const handleMessageChanged = (message) => {
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
logLocal("handleMessageChanged", message);
|
||||||
|
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: client.cache.identify({ __typename: "conversations", id: message.conversationid }),
|
id: client.cache.identify({ __typename: "conversations", id: message.conversationid }),
|
||||||
fields: {
|
fields: {
|
||||||
@@ -118,8 +135,12 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleConversationChanged = (data) => {
|
const handleConversationChanged = (data) => {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
const { conversationId, type, job_conversations, ...fields } = data;
|
const { conversationId, type, job_conversations, ...fields } = data;
|
||||||
|
|
||||||
|
logLocal("handleConversationChanged", data);
|
||||||
|
|
||||||
// Identify the conversation in the Apollo cache
|
// Identify the conversation in the Apollo cache
|
||||||
const cacheId = client.cache.identify({
|
const cacheId = client.cache.identify({
|
||||||
__typename: "conversations",
|
__typename: "conversations",
|
||||||
@@ -161,23 +182,60 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleNewMessage = ({ conversationId, message }) => {
|
const handleNewMessage = ({ conversationId, message }) => {
|
||||||
|
if (!conversationId || !message.id || !message.text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logLocal("handleNewMessage", { conversationId, message });
|
||||||
|
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
||||||
fields: {
|
fields: {
|
||||||
messages(existing = []) {
|
messages(existing = []) {
|
||||||
|
// Ensure that the `message` object matches the schema
|
||||||
const newMessageRef = client.cache.writeFragment({
|
const newMessageRef = client.cache.writeFragment({
|
||||||
data: message,
|
data: {
|
||||||
|
__typename: "messages",
|
||||||
|
id: message.id,
|
||||||
|
body: message.text,
|
||||||
|
selectedMedia: message.image_path || [],
|
||||||
|
imexshopid: message.userid,
|
||||||
|
status: message.status,
|
||||||
|
created_at: message.created_at,
|
||||||
|
read: message.read
|
||||||
|
},
|
||||||
fragment: gql`
|
fragment: gql`
|
||||||
fragment NewMessage on messages {
|
fragment NewMessage on messages {
|
||||||
id
|
id
|
||||||
body
|
body
|
||||||
createdAt
|
|
||||||
selectedMedia
|
selectedMedia
|
||||||
imexshopid
|
imexshopid
|
||||||
|
status
|
||||||
|
created_at
|
||||||
|
read
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
return [...existing, newMessageRef];
|
|
||||||
|
// Prevent duplicates by checking if the message already exists
|
||||||
|
const isDuplicate = existing.some(
|
||||||
|
(msgRef) =>
|
||||||
|
client.cache.readFragment({
|
||||||
|
id: msgRef.__ref,
|
||||||
|
fragment: gql`
|
||||||
|
fragment CheckMessage on messages {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})?.id === message.id
|
||||||
|
);
|
||||||
|
|
||||||
|
// We already have it, so return the existing list
|
||||||
|
if (isDuplicate) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...existing, newMessageRef]; // Add the new message reference
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,31 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
||||||
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
|
||||||
export default function ChatArchiveButton({ conversation }) {
|
export default function ChatArchiveButton({ conversation, bodyshop }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE);
|
const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE);
|
||||||
|
const { socket } = useContext(SocketContext);
|
||||||
|
|
||||||
const handleToggleArchive = async () => {
|
const handleToggleArchive = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
await updateConversation({
|
const updatedConversation = await updateConversation({
|
||||||
variables: { id: conversation.id, archived: !conversation.archived },
|
variables: { id: conversation.id, archived: !conversation.archived }
|
||||||
refetchQueries: ["CONVERSATION_LIST_QUERY"]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (socket) {
|
||||||
|
socket.emit("conversation-modified", {
|
||||||
|
conversationId: conversation.id,
|
||||||
|
bodyshopId: bodyshop.id,
|
||||||
|
archived: updatedConversation.data.update_conversations_by_pk.archived
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default function ChatConversationTitle({ conversation, bodyshop }) {
|
|||||||
bodyshop={bodyshop}
|
bodyshop={bodyshop}
|
||||||
/>
|
/>
|
||||||
<ChatTagRoContainer conversation={conversation || []} bodyshop={bodyshop} />
|
<ChatTagRoContainer conversation={conversation || []} bodyshop={bodyshop} />
|
||||||
<ChatArchiveButton conversation={conversation} />
|
<ChatArchiveButton conversation={conversation} bodyshop={bodyshop} />
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
|||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only"
|
nextFetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
|
|
||||||
const { socket } = useContext(SocketContext);
|
const { socket } = useContext(SocketContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -50,10 +50,11 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
|
|||||||
};
|
};
|
||||||
sendMessage(newMessage);
|
sendMessage(newMessage);
|
||||||
if (socket) {
|
if (socket) {
|
||||||
|
const lastMessage = conversation.messages?.[conversation.messages.length - 1]; // Get the last message
|
||||||
socket.emit("message-added", {
|
socket.emit("message-added", {
|
||||||
conversationId: conversation.id,
|
conversationId: conversation.id,
|
||||||
bodyshopId: bodyshop.id,
|
bodyshopId: bodyshop.id,
|
||||||
message: newMessage
|
message: lastMessage
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setSelectedMedia(
|
setSelectedMedia(
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ exports.status = async (req, res) => {
|
|||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Ignore status 'queued'
|
||||||
|
if (SmsStatus === "queued") {
|
||||||
|
return res.status(200).json({ message: "Status 'queued' disregarded." });
|
||||||
|
}
|
||||||
|
|
||||||
// Update message status in the database
|
// Update message status in the database
|
||||||
const response = await client.request(queries.UPDATE_MESSAGE_STATUS, {
|
const response = await client.request(queries.UPDATE_MESSAGE_STATUS, {
|
||||||
msid: SmsSid,
|
msid: SmsSid,
|
||||||
@@ -47,6 +52,7 @@ exports.status = async (req, res) => {
|
|||||||
warning: "No message returned from the database update."
|
warning: "No message returned from the database update."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("sms-status-update-error", "ERROR", "api", null, {
|
logger.log("sms-status-update-error", "ERROR", "api", null, {
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ const redisSocketEvents = ({
|
|||||||
const messageAdded = ({ bodyshopId, conversationId, message }) => {
|
const messageAdded = ({ bodyshopId, conversationId, message }) => {
|
||||||
try {
|
try {
|
||||||
const room = getBodyshopConversationRoom({ bodyshopId, conversationId });
|
const room = getBodyshopConversationRoom({ bodyshopId, conversationId });
|
||||||
io.to(room).emit("new-message", message);
|
io.to(room).emit("new-message", { message, conversationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("Failed to handle new message", "error", "io-redis", null, {
|
logger.log("Failed to handle new message", "error", "io-redis", null, {
|
||||||
error: error.message,
|
error: error.message,
|
||||||
|
|||||||
Reference in New Issue
Block a user