From 17c2427413ccfb039c5bd67d3294d1aeef78c200 Mon Sep 17 00:00:00 2001 From: slawk0 Date: Sat, 4 Jan 2025 23:35:10 +0100 Subject: [PATCH] fixed (I hope so) insertContact function --- .../chat/chatHeader/ContactProfile.tsx | 5 +- client/src/utils/AuthProvider.tsx | 4 + server/db/db.js | 301 ++++++++++++------ server/server.js | 15 +- 4 files changed, 221 insertions(+), 104 deletions(-) diff --git a/client/src/components/chat/chatHeader/ContactProfile.tsx b/client/src/components/chat/chatHeader/ContactProfile.tsx index 21b15cb..4952a9b 100644 --- a/client/src/components/chat/chatHeader/ContactProfile.tsx +++ b/client/src/components/chat/chatHeader/ContactProfile.tsx @@ -15,7 +15,10 @@ function ContactProfile() { profile img )} -

{currentContact ? currentContact.username : null}

+

+ {currentContact ? currentContact.username : null} user id:{' '} + {currentContact?.user_id} conv id: {currentContact?.conversation_id} +

diff --git a/client/src/utils/AuthProvider.tsx b/client/src/utils/AuthProvider.tsx index 74b1874..0338a02 100644 --- a/client/src/utils/AuthProvider.tsx +++ b/client/src/utils/AuthProvider.tsx @@ -34,6 +34,10 @@ export function AuthProvider({ children }: { children: ReactNode }) { withCredentials: true, }); setUser({ username: res.data.username, id: res.data.user_id }); + console.info('user: : ', { + username: res.data.username, + id: res.data.user_id, + }); setAuthorized(true); } catch (err) { setAuthorized(false); diff --git a/server/db/db.js b/server/db/db.js index 82fe499..b93f5f6 100644 --- a/server/db/db.js +++ b/server/db/db.js @@ -474,14 +474,37 @@ async function changePassword(username, newPasswordHash) { } async function insertContactById(senderId, conversation_id, read) { - const query = ` - INSERT INTO Contacts (user_id, conversation_id, read) - VALUES( $1, $2, $3) + // First check if contact already exists + const checkQuery = ` + SELECT contact_id, conversation_id, user_id, read, last_active + FROM Contacts + WHERE user_id = $1 AND conversation_id = $2 `; try { - const result = await client.query(query, [senderId, conversation_id, read]); - console.log("Insertcontactbyid: ", senderId, conversation_id); + // Check if contact already exists + const existingContact = await client.query(checkQuery, [ + senderId, + conversation_id, + ]); + if (existingContact.rows.length > 0) { + console.log("Contact already exists:", senderId, conversation_id); + return existingContact.rows[0]; + } + + // If contact doesn't exist, create new one + const insertQuery = ` + INSERT INTO Contacts (user_id, conversation_id, read) + VALUES($1, $2, $3) + RETURNING contact_id, conversation_id, user_id, read, last_active + `; + + const result = await client.query(insertQuery, [ + senderId, + conversation_id, + read, + ]); + console.log("Insert contact by id:", senderId, conversation_id); return result.rows[0] || null; } catch (error) { console.error("Failed to insert contact by IDs:", error); @@ -489,49 +512,35 @@ async function insertContactById(senderId, conversation_id, read) { } } -async function insertContact(userUsername, receiverUsername, read) { +async function insertContact(initiatorId, receiverId, contactUsername, read) { try { - // 1. Get user IDs - const getUsersQuery = ` - SELECT - u1.user_id AS initiator_id, - u1.username AS initiator_username, - u2.user_id AS contact_id, - u2.username AS contact_username - FROM Accounts u1 - CROSS JOIN Accounts u2 - WHERE LOWER(u1.username) = LOWER($1) - AND LOWER(u2.username) = LOWER($2) - `; - const usersResult = await client.query(getUsersQuery, [ - userUsername, - receiverUsername, - ]); - if (!usersResult.rows[0]) { - console.error("Users not found"); - return null; - } - const { initiator_id, contact_id, contact_username } = usersResult.rows[0]; - - // 2. Find existing conversation const findConversationQuery = ` - SELECT c.conversation_id - FROM Conversations c - JOIN Memberships m1 ON c.conversation_id = m1.conversation_id - JOIN Memberships m2 ON c.conversation_id = m2.conversation_id - WHERE c.conversation_type = 'direct' - AND m1.user_id = $1 - AND m2.user_id = $2 - LIMIT 1 + SELECT c.conversation_id + FROM Conversations c + JOIN Memberships m1 ON c.conversation_id = m1.conversation_id + JOIN Memberships m2 ON c.conversation_id = m2.conversation_id + WHERE c.conversation_type = 'direct' + AND ( + (m1.user_id = $1 AND m2.user_id = $2) + OR + (m1.user_id = $2 AND m2.user_id = $1) + ) + AND ( + SELECT COUNT(DISTINCT user_id) + FROM Memberships + WHERE conversation_id = c.conversation_id + ) = 1 -- Allow a conversation with a single user (self) + LIMIT 1 `; const conversationResult = await client.query(findConversationQuery, [ - initiator_id, - contact_id, + initiatorId, + receiverId, ]); let conversation_id = conversationResult.rows[0]?.conversation_id; - // 3. Create new conversation if none exists + // Create new conversation if none exists if (!conversation_id) { + console.error("CONVERSATION DON'T EXIST: ", conversation_id); const createConversationQuery = ` INSERT INTO Conversations (conversation_type) VALUES ('direct') @@ -539,8 +548,8 @@ async function insertContact(userUsername, receiverUsername, read) { `; const newConversationResult = await client.query(createConversationQuery); conversation_id = newConversationResult.rows[0].conversation_id; - - // 4. Create memberships for both users + console.error("NEW CONVERSATION ID: ", conversation_id); + // Create memberships for both users (or single user if they are the same) const createMembershipsQuery = ` INSERT INTO Memberships (conversation_id, user_id) VALUES ($1, $2), ($1, $3) @@ -548,27 +557,18 @@ async function insertContact(userUsername, receiverUsername, read) { `; await client.query(createMembershipsQuery, [ conversation_id, - initiator_id, - contact_id, + initiatorId, + receiverId, ]); } - // 5. Create or update contact - const upsertContactQuery = ` - INSERT INTO Contacts (user_id, conversation_id, read) - VALUES ($1, $2, $3) - ON CONFLICT (user_id, conversation_id) - DO UPDATE SET last_active = CURRENT_TIMESTAMP, read = $3 - RETURNING contact_id, conversation_id, user_id, read, last_active - `; - const contactResult = await client.query(upsertContactQuery, [ - initiator_id, - conversation_id, - read, - ]); - const contact = contactResult.rows[0]; + const contact = await insertContactById(initiatorId, conversation_id, read); + if (!contact.contact_id) { + console.error("Failed to insert contact by id"); + return null; + } - // 6. Retrieve the last message, last active time, and last message sender + // Retrieve the last message, last active time, and last message sender const lastMessageQuery = ` SELECT DISTINCT ON (m.conversation_id) m.content AS last_message, @@ -590,18 +590,18 @@ async function insertContact(userUsername, receiverUsername, read) { lastMessageSender = lastMessageResult.rows[0].last_message_sender; } - // 7. Return formatted result with contact's user_id, last message, last active time, and last message sender + // Return formatted result with contact's user_id, last message, last active time, and last message sender return { id: contact.contact_id, - user_id: contact_id, // Now using the contact's user_id instead of the initiator's - username: contact_username, + user_id: receiverId, // Now using the contact's user_id instead of the initiator's + username: contactUsername, last_active: contact.last_active, conversation_id: contact.conversation_id, type: "direct", read: contact.read, - last_message: lastMessage, - last_message_time: lastMessageTime, - last_message_sender: lastMessageSender, + last_message: lastMessage || null, + last_message_time: lastMessageTime || null, + last_message_sender: lastMessageSender || null, }; } catch (error) { console.error("Failed to insert contact:", error); @@ -609,53 +609,158 @@ async function insertContact(userUsername, receiverUsername, read) { } } +async function getLatestMessage(conversation_id) { + const latestMessageQuery = ` + WITH LatestMessage AS ( + SELECT + m.message_id as id, + m.user_id, + m.content as last_message, + m.sent_at as last_message_time, + a.username as last_message_sender + FROM Messages m + JOIN Accounts a ON m.user_id = a.user_id + WHERE m.conversation_id = $1 + ORDER BY m.sent_at DESC + LIMIT 1 + ), + ConversationUsers AS ( + SELECT + m.user_id, + a.username, + c.read + FROM Memberships m + JOIN Accounts a ON m.user_id = a.user_id + LEFT JOIN Contacts c ON c.user_id = m.user_id AND c.conversation_id = $1 + WHERE m.conversation_id = $1 + LIMIT 1 + ) + SELECT + lm.*, + cu.user_id, + cu.username, + cu.read, + c.conversation_type as type, + c.last_active, + c.conversation_id + FROM Conversations c + LEFT JOIN LatestMessage lm ON TRUE + LEFT JOIN ConversationUsers cu ON TRUE + WHERE c.conversation_id = $1; + `; + + try { + // Get latest message and details + const result = await client.query(latestMessageQuery, [conversation_id]); + + if (result.rows.length === 0) { + console.error("No messages found in conversation:", conversation_id); + return null; + } + + const row = result.rows[0]; + + return { + id: row.id, + user_id: row.user_id, + username: row.username, + last_active: row.last_active, + conversation_id: row.conversation_id, + type: row.type, + read: row.read, + last_message: row.last_message, + last_message_time: row.last_message_time, + last_message_sender: row.last_message_sender, + }; + } catch (error) { + console.error("Failed to get latest message:", error); + return null; + } +} + +async function checkMembership(user_id, conversation_id) { + const query = ` + SELECT EXISTS ( + SELECT 1 + FROM Memberships + WHERE user_id = $1 + AND conversation_id = $2 + ) AS is_member; + `; + + try { + const result = await client.query(query, [user_id, conversation_id]); + return result.rows[0].is_member; + } catch (error) { + console.error("Error checking membership:", error); + return false; + } +} + async function getContacts(user_id) { const contactsQuery = ` WITH LastMessages AS ( SELECT DISTINCT ON (m.conversation_id) m.conversation_id, - m.content as last_message, - m.sent_at as last_message_time, - a.username as last_message_sender + m.content AS last_message, + m.sent_at AS last_message_time, + a.username AS last_message_sender FROM Messages m JOIN Accounts a ON m.user_id = a.user_id ORDER BY m.conversation_id, m.sent_at DESC + ), + DirectContacts AS ( + SELECT + c.contact_id AS id, + a.user_id AS user_id, + a.username AS username, + conv.last_active, + c.conversation_id, + conv.conversation_type AS type, + c.read, + lm.last_message, + lm.last_message_time, + lm.last_message_sender + FROM Contacts c + JOIN Conversations conv ON c.conversation_id = conv.conversation_id + JOIN Memberships m ON m.conversation_id = c.conversation_id + JOIN Accounts a ON a.user_id = m.user_id + LEFT JOIN LastMessages lm ON c.conversation_id = lm.conversation_id + WHERE c.user_id = $1 + AND conv.conversation_type = 'direct' + AND a.user_id != $1 + ORDER BY c.conversation_id, conv.last_active DESC + ), + GroupContacts AS ( + SELECT + c.contact_id AS id, + NULL::uuid AS user_id, + conv.name AS username, + conv.last_active, + c.conversation_id, + conv.conversation_type AS type, + c.read, + lm.last_message, + lm.last_message_time, + lm.last_message_sender + FROM Contacts c + JOIN Conversations conv ON c.conversation_id = conv.conversation_id + LEFT JOIN LastMessages lm ON c.conversation_id = lm.conversation_id + WHERE c.user_id = $1 + AND conv.conversation_type = 'group' + ORDER BY c.conversation_id, conv.last_active DESC ) - SELECT DISTINCT ON (c.conversation_id) - c.contact_id AS id, - CASE - WHEN conv.conversation_type = 'group' THEN NULL - ELSE a2.user_id - END AS user_id, - CASE - WHEN conv.conversation_type = 'group' THEN conv.name - ELSE a2.username - END AS username, - conv.last_active, - c.conversation_id, - conv.conversation_type AS type, - c.read, - lm.last_message, - lm.last_message_time, - lm.last_message_sender - FROM Contacts c - JOIN Conversations conv ON c.conversation_id = conv.conversation_id - JOIN Memberships m ON m.conversation_id = c.conversation_id - JOIN Accounts a2 ON a2.user_id = m.user_id - LEFT JOIN LastMessages lm ON c.conversation_id = lm.conversation_id - WHERE c.user_id = $1 - AND ( - conv.conversation_type = 'direct' - OR - conv.conversation_type = 'group' - ) - ORDER BY c.conversation_id, conv.last_active DESC; + SELECT * FROM DirectContacts + UNION ALL + SELECT * FROM GroupContacts + ORDER BY last_active DESC; `; try { // Execute the query with the user_id parameter const contactsResult = await client.query(contactsQuery, [user_id]); - console.error(contactsResult.rows); + console.log(contactsResult.rows); // Debugging: log the results to verify + // Map the results to a more friendly format const contacts = contactsResult.rows.map((row) => ({ id: row.id, diff --git a/server/server.js b/server/server.js index f25ba24..cf1cf26 100644 --- a/server/server.js +++ b/server/server.js @@ -250,17 +250,22 @@ app.post("/api/chat/contact/:contact", authorizeUser, async (req, res) => { if (!req.params.contact) { return res.status(400).json({ message: "Missing contact parameter" }); } - const usernameContact = req.params.contact; - + const contactUsername = req.params.contact; // Validate username for invalid characters, length, and type - if (!isValidUsername(usernameContact)) { + if (!isValidUsername(contactUsername)) { return res.status(400).json({ message: "Invalid username provided" }); } + // Get contact user id and check if exist + const contact = await getUserId(contactUsername); + if (!contact) { + return res.status(400).json({ message: "User does not exist" }); + } try { const result = await insertContact( - req.user.username, - usernameContact, + req.user.user_id, + contact.user_id, + contactUsername, true, ); if (result === null) {