From 657720cb10a914302b84dfbc4d7451303ab58fdb Mon Sep 17 00:00:00 2001 From: Dave Date: Tue, 30 Dec 2025 13:42:04 -0500 Subject: [PATCH] Initial --- server/routes/smsRoutes.js | 3 +- server/sms/status.js | 115 ++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/server/routes/smsRoutes.js b/server/routes/smsRoutes.js index c09cc1632..19931318d 100644 --- a/server/routes/smsRoutes.js +++ b/server/routes/smsRoutes.js @@ -3,7 +3,7 @@ const router = express.Router(); const twilio = require("twilio"); const { receive } = require("../sms/receive"); const { send } = require("../sms/send"); -const { status, markConversationRead } = require("../sms/status"); +const { status, markConversationRead, markLastMessageUnread } = require("../sms/status"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); // Twilio Webhook Middleware for production @@ -14,5 +14,6 @@ router.post("/receive", twilioWebhookMiddleware, receive); router.post("/send", validateFirebaseIdTokenMiddleware, send); router.post("/status", twilioWebhookMiddleware, status); router.post("/markConversationRead", validateFirebaseIdTokenMiddleware, markConversationRead); +router.post("/markLastMessageUnread", validateFirebaseIdTokenMiddleware, markLastMessageUnread); module.exports = router; diff --git a/server/sms/status.js b/server/sms/status.js index 437da73ab..7dc11d65b 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -8,6 +8,46 @@ const { const logger = require("../utils/logger"); const { phone } = require("phone"); +// Local GraphQL for “mark unread” (kept here to avoid requiring edits to graphql-client/queries.js) +const GET_LAST_INBOUND_NON_SYSTEM_MESSAGE = ` + query GetLastInboundNonSystemMessage($conversationId: uuid!) { + messages( + where: { + conversationid: { _eq: $conversationId } + isoutbound: { _eq: false } + is_system: { _eq: false } + } + order_by: { created_at: desc } + limit: 1 + ) { + id + created_at + read + } + } +`; + +const MARK_LAST_INBOUND_MESSAGE_UNREAD_MAX_ONE = ` + mutation MarkLastInboundMessageUnreadMaxOne($conversationId: uuid!, $lastId: uuid!) { + markOthersRead: update_messages( + _set: { read: true } + where: { + conversationid: { _eq: $conversationId } + isoutbound: { _eq: false } + is_system: { _eq: false } + id: { _neq: $lastId } + } + ) { + affected_rows + returning { id } + } + + markLastUnread: update_messages_by_pk(pk_columns: { id: $lastId }, _set: { read: false }) { + id + } + } +`; + /** * Handle the status of an SMS message * @param req @@ -176,7 +216,80 @@ const markConversationRead = async (req, res) => { } }; +/** + * Mark last inbound (customer) non-system message as unread. + * Enforces: max unread inbound non-system messages per thread = 1 + * + * Body: { conversationId, imexshopid, bodyshopid } + */ +const markLastMessageUnread = async (req, res) => { + const { + ioRedis, + ioHelpers: { getBodyshopRoom } + } = req; + + const { conversationId, imexshopid, bodyshopid } = req.body; + + if (!conversationId || !imexshopid || !bodyshopid) { + return res.status(400).json({ error: "Invalid conversation data provided." }); + } + + try { + const lastResp = await client.request(GET_LAST_INBOUND_NON_SYSTEM_MESSAGE, { conversationId }); + const last = lastResp?.messages?.[0]; + + if (!last?.id) { + // No inbound message to mark unread + const broadcastRoom = getBodyshopRoom(bodyshopid); + ioRedis.to(broadcastRoom).emit("conversation-changed", { + type: "conversation-marked-unread", + conversationId, + lastUnreadMessageId: null, + messageIdsMarkedRead: [], + unreadCount: 0 + }); + + return res.status(200).json({ + success: true, + conversationId, + lastUnreadMessageId: null, + messageIdsMarkedRead: [], + unreadCount: 0 + }); + } + + const mutResp = await client.request(MARK_LAST_INBOUND_MESSAGE_UNREAD_MAX_ONE, { + conversationId, + lastId: last.id + }); + + const messageIdsMarkedRead = mutResp?.markOthersRead?.returning?.map((m) => m.id) || []; + + const broadcastRoom = getBodyshopRoom(bodyshopid); + + ioRedis.to(broadcastRoom).emit("conversation-changed", { + type: "conversation-marked-unread", + conversationId, + lastUnreadMessageId: last.id, + messageIdsMarkedRead, + unreadCount: 1 + }); + + return res.status(200).json({ + success: true, + conversationId, + lastUnreadMessageId: last.id, + messageIdsMarkedRead, + unreadCount: 1 + }); + } catch (error) { + console.error("Error marking last message unread:", error); + return res.status(500).json({ error: "Failed to mark last message unread." }); + } +}; + module.exports = { status, - markConversationRead + markConversationRead, + markLastMessageUnread };