adjusted code to handle getMessage function and other by conversation_id (groups partially are working 😲)
This commit is contained in:
@@ -39,7 +39,7 @@ export async function sendContact(contact: string) {
|
||||
}
|
||||
|
||||
export async function getMessages(
|
||||
contact: string | null,
|
||||
contact: number | null,
|
||||
cursor: number | null = 0,
|
||||
limit: number = 50,
|
||||
): Promise<{ messages: ChatMessages[] }> {
|
||||
|
||||
@@ -27,7 +27,7 @@ function AddGroupMember({ contact }: AddGroupMemberProps) {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await axiosClient.post(`/api/chat/groups/add/`, {
|
||||
group_id: contact?.group_id,
|
||||
group_id: contact?.conversation_id,
|
||||
username: data.username,
|
||||
});
|
||||
console.log(response.data);
|
||||
|
||||
@@ -74,7 +74,7 @@ function ContactsList({
|
||||
InitializeContact(contact);
|
||||
updateContactStatus(contact, true);
|
||||
}}
|
||||
key={contact.id || contact.group_id}
|
||||
key={contact.conversation_id}
|
||||
>
|
||||
<span className="flex-shrink-0">
|
||||
{/*user_id: {contact.user_id}, contact id: {contact.id}, group_id: {contact.group_id} username: */}
|
||||
|
||||
@@ -15,12 +15,12 @@ type Input = {
|
||||
};
|
||||
|
||||
type MessageFormProps = {
|
||||
contactUsername: ContactsProps;
|
||||
contact: ContactsProps;
|
||||
setMessages: React.Dispatch<React.SetStateAction<ChatMessages[]>>;
|
||||
messages: ChatMessages[];
|
||||
};
|
||||
|
||||
const MessageForm = ({ contactUsername, setMessages }: MessageFormProps) => {
|
||||
const MessageForm = ({ contact, setMessages }: MessageFormProps) => {
|
||||
const { username }: { username: string } = useOutletContext();
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
@@ -100,7 +100,7 @@ const MessageForm = ({ contactUsername, setMessages }: MessageFormProps) => {
|
||||
const tempMessage: ChatMessages = {
|
||||
sender: username,
|
||||
message: data.message.trim(),
|
||||
recipient: contactUsername.username,
|
||||
recipient: contact.conversation_id,
|
||||
message_id: 0, // Set to 0 because of key={msg.message_id || msg.tempId} in messages list
|
||||
pending: true,
|
||||
tempId: tempId,
|
||||
@@ -115,7 +115,7 @@ const MessageForm = ({ contactUsername, setMessages }: MessageFormProps) => {
|
||||
'chat message',
|
||||
{
|
||||
message: data.message.trim(),
|
||||
recipient: contactUsername.username,
|
||||
recipient: contact.conversation_id,
|
||||
tempId: tempId,
|
||||
attachment_url: attachmentUrl,
|
||||
},
|
||||
@@ -151,7 +151,7 @@ const MessageForm = ({ contactUsername, setMessages }: MessageFormProps) => {
|
||||
|
||||
console.log('sent message: ', {
|
||||
message: data.message.trim(),
|
||||
recipient: contactUsername,
|
||||
recipient: contact.conversation_id,
|
||||
tempId: tempId,
|
||||
attachment_url: attachmentUrl,
|
||||
});
|
||||
@@ -197,8 +197,8 @@ const MessageForm = ({ contactUsername, setMessages }: MessageFormProps) => {
|
||||
}}
|
||||
className={`w-full overflow-y-hidden resize-none bg-green-50 text-black rounded-md p-2 min-h-[40px] max-h-96
|
||||
${isOverLimit ? 'border-2 border-red-500' : isNearLimit ? 'border-2 border-yellow-500' : ''} mx-auto`}
|
||||
autoFocus={!!contactUsername}
|
||||
disabled={!contactUsername}
|
||||
autoFocus={!!contact}
|
||||
disabled={!contact}
|
||||
placeholder="Enter message"
|
||||
onKeyDown={handleKeyPress}
|
||||
rows={1}
|
||||
|
||||
@@ -15,7 +15,7 @@ type MessagesAreaProps = {
|
||||
updateContactStatus: (contact: ContactsProps, read: boolean) => void;
|
||||
messageHandler: (msg: ChatMessages) => void;
|
||||
setContactsList: React.Dispatch<React.SetStateAction<ContactsProps[]>>;
|
||||
fetchPreviousMessages: (contact: string | null) => Promise<void>;
|
||||
fetchPreviousMessages: (contact: number | null) => Promise<void>;
|
||||
errorMessage: string | null;
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ function MessagesArea({
|
||||
setIsLoading(true);
|
||||
prevScrollHeight.current = container.scrollHeight;
|
||||
setIsFetchingHistory(true);
|
||||
fetchPreviousMessages(currentContact.username)
|
||||
fetchPreviousMessages(currentContact.conversation_id)
|
||||
.then(() => setIsLoading(false))
|
||||
.catch(() => {
|
||||
//console.error('Failed to fetch messages: ', e);
|
||||
@@ -83,7 +83,7 @@ function MessagesArea({
|
||||
id: msg.message_id,
|
||||
user_id: msg.sender_id,
|
||||
type: 'direct',
|
||||
group_id: null,
|
||||
conversation_id: msg.conversation_id,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getMessages, setContactStatus } from '../api/contactsApi.tsx';
|
||||
export type ChatMessages = {
|
||||
sender: string;
|
||||
message: string;
|
||||
recipient: string;
|
||||
recipient: number; //conversation_id
|
||||
message_id: number;
|
||||
tempId: string;
|
||||
pending: boolean;
|
||||
@@ -27,7 +27,7 @@ export type ContactsProps = {
|
||||
user_id: number;
|
||||
id: number;
|
||||
type: 'direct' | 'group';
|
||||
group_id: number | null;
|
||||
conversation_id: number;
|
||||
};
|
||||
|
||||
function Chat() {
|
||||
@@ -64,7 +64,7 @@ function Chat() {
|
||||
setCurrentContact(newContact); // Set state for current user (used in contactProfile.tsx and for filtering messages)
|
||||
console.log('Initialized contact');
|
||||
|
||||
fetchMessages(newContact.username).catch((e) =>
|
||||
fetchMessages(newContact.conversation_id).catch((e) =>
|
||||
console.error('Failed to fetch messages: ', e),
|
||||
);
|
||||
setContactsList((prevContacts) =>
|
||||
@@ -76,7 +76,7 @@ function Chat() {
|
||||
console.log('Current contact is now: ', newContact);
|
||||
}
|
||||
|
||||
const fetchMessages = async (currentContact: string | null) => {
|
||||
const fetchMessages = async (currentContact: number) => {
|
||||
console.log('Fetching messages for: ', currentContact);
|
||||
|
||||
await getMessages(currentContact)
|
||||
@@ -92,7 +92,7 @@ function Chat() {
|
||||
.catch((error) => setErrorMessage(error.response.data.message));
|
||||
};
|
||||
|
||||
const fetchPreviousMessages = async (contact: string | null) => {
|
||||
const fetchPreviousMessages = async (contact: number | null) => {
|
||||
console.log('Fetching messages for: ', contact, 'with cursor: ', cursor);
|
||||
|
||||
await getMessages(contact, cursor, 50)
|
||||
@@ -182,7 +182,7 @@ function Chat() {
|
||||
<div className="flex-shrink-0 mb-2 mt-0">
|
||||
{currentContact && currentContact.username?.length >= 4 ? (
|
||||
<MessageForm
|
||||
contactUsername={currentContact}
|
||||
contact={currentContact}
|
||||
setMessages={setMessages}
|
||||
messages={messages}
|
||||
/>
|
||||
|
||||
257
server/db/db.js
257
server/db/db.js
@@ -153,86 +153,25 @@ async function getUserId(username) {
|
||||
}
|
||||
|
||||
async function insertMessage(
|
||||
senderUsername,
|
||||
receiverUsername,
|
||||
senderId,
|
||||
conversation_id,
|
||||
content,
|
||||
attachmentUrl,
|
||||
) {
|
||||
console.log(
|
||||
`senderUsername: ${senderUsername}, receiverUsername: ${receiverUsername}, content: ${content}, attachmentUrl: ${attachmentUrl} `,
|
||||
`senderId: ${senderId}, conversation_id: ${conversation_id}, content: ${content}, attachmentUrl: ${attachmentUrl}`,
|
||||
);
|
||||
|
||||
const query = `
|
||||
WITH sender AS (
|
||||
SELECT user_id
|
||||
FROM Accounts
|
||||
WHERE username = $1
|
||||
LIMIT 1
|
||||
),
|
||||
recipient AS (
|
||||
SELECT user_id
|
||||
FROM Accounts
|
||||
WHERE username = $2
|
||||
LIMIT 1
|
||||
),
|
||||
conversation AS (
|
||||
SELECT conversation_id
|
||||
FROM Conversations
|
||||
WHERE conversation_type = 'direct'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = Conversations.conversation_id
|
||||
AND user_id = (SELECT user_id FROM sender)
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = Conversations.conversation_id
|
||||
AND user_id = (SELECT user_id FROM recipient)
|
||||
)
|
||||
LIMIT 1
|
||||
),
|
||||
new_conversation AS (
|
||||
INSERT INTO Conversations (conversation_type)
|
||||
SELECT 'direct'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM conversation)
|
||||
RETURNING conversation_id
|
||||
),
|
||||
final_conversation AS (
|
||||
SELECT conversation_id FROM conversation
|
||||
UNION ALL
|
||||
SELECT conversation_id FROM new_conversation
|
||||
),
|
||||
insert_memberships AS (
|
||||
INSERT INTO Memberships (conversation_id, user_id)
|
||||
SELECT conversation_id, user_id
|
||||
FROM final_conversation, sender
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = final_conversation.conversation_id
|
||||
AND user_id = sender.user_id
|
||||
)
|
||||
UNION ALL
|
||||
SELECT conversation_id, user_id
|
||||
FROM final_conversation, recipient
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = final_conversation.conversation_id
|
||||
AND user_id = recipient.user_id
|
||||
)
|
||||
)
|
||||
INSERT INTO Messages (conversation_id, user_id, content, attachment_url)
|
||||
SELECT conversation_id, sender.user_id, $3, $4
|
||||
FROM final_conversation, sender
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING message_id, content, sent_at, attachment_url, user_id AS sender_id, conversation_id;
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await client.query(query, [
|
||||
senderUsername,
|
||||
receiverUsername,
|
||||
conversation_id,
|
||||
senderId,
|
||||
content,
|
||||
attachmentUrl,
|
||||
]);
|
||||
@@ -314,98 +253,65 @@ async function addMemberToGroupByUsername(conversation_id, username) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessages(user_id, receiverUsername, limit = 50, cursor = 0) {
|
||||
let query = `
|
||||
WITH recipient AS (
|
||||
SELECT user_id
|
||||
FROM Accounts
|
||||
WHERE username = $2
|
||||
),
|
||||
conversation AS (
|
||||
SELECT conversation_id
|
||||
FROM Conversations
|
||||
WHERE conversation_type = 'direct'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = Conversations.conversation_id
|
||||
AND user_id = $1
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = Conversations.conversation_id
|
||||
AND user_id = (SELECT user_id FROM recipient)
|
||||
)
|
||||
LIMIT 1
|
||||
)
|
||||
SELECT
|
||||
m.message_id,
|
||||
m.content AS message,
|
||||
m.sent_at,
|
||||
m.attachment_url,
|
||||
a.username AS sender,
|
||||
r.username AS recipient
|
||||
FROM Messages m
|
||||
JOIN Accounts a ON m.user_id = a.user_id
|
||||
JOIN Memberships mem ON m.conversation_id = mem.conversation_id AND mem.user_id = (SELECT user_id FROM recipient)
|
||||
JOIN Accounts r ON m.user_id = r.user_id
|
||||
WHERE m.conversation_id = (SELECT conversation_id FROM conversation)
|
||||
AND m.message_id < $3
|
||||
ORDER BY m.message_id DESC
|
||||
LIMIT $4;
|
||||
async function getMessages(user_id, conversation_id, limit = 50, cursor = 0) {
|
||||
// Check if the user is a member of the conversation
|
||||
const checkMembershipQuery = `
|
||||
SELECT 1 FROM Memberships
|
||||
WHERE conversation_id = $1 AND user_id = $2
|
||||
LIMIT 1;
|
||||
`;
|
||||
|
||||
let params = [user_id, receiverUsername, cursor, limit];
|
||||
|
||||
if (!cursor) {
|
||||
query = `
|
||||
WITH recipient AS (
|
||||
SELECT user_id
|
||||
FROM Accounts
|
||||
WHERE username = $2
|
||||
),
|
||||
conversation AS (
|
||||
SELECT conversation_id
|
||||
FROM Conversations
|
||||
WHERE conversation_type = 'direct'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = Conversations.conversation_id
|
||||
AND user_id = $1
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = Conversations.conversation_id
|
||||
AND user_id = (SELECT user_id FROM recipient)
|
||||
)
|
||||
LIMIT 1
|
||||
)
|
||||
SELECT
|
||||
m.message_id,
|
||||
m.content AS message,
|
||||
m.sent_at,
|
||||
m.attachment_url,
|
||||
a.username AS sender,
|
||||
r.username AS recipient
|
||||
FROM Messages m
|
||||
JOIN Accounts a ON m.user_id = a.user_id
|
||||
JOIN Memberships mem ON m.conversation_id = mem.conversation_id AND mem.user_id = (SELECT user_id FROM recipient)
|
||||
JOIN Accounts r ON m.user_id = r.user_id
|
||||
WHERE m.conversation_id = (SELECT conversation_id FROM conversation)
|
||||
ORDER BY m.message_id DESC
|
||||
LIMIT $3;
|
||||
`;
|
||||
params = [user_id, receiverUsername, limit];
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await client.query(query, params);
|
||||
const checkResult = await client.query(checkMembershipQuery, [
|
||||
conversation_id, // $1
|
||||
user_id, // $2
|
||||
]);
|
||||
if (checkResult.rows.length === 0) {
|
||||
console.error("User is not a member of the conversation");
|
||||
return [];
|
||||
}
|
||||
|
||||
let query;
|
||||
let params;
|
||||
|
||||
if (cursor) {
|
||||
query = `
|
||||
SELECT
|
||||
m.message_id,
|
||||
m.content AS message,
|
||||
m.sent_at,
|
||||
m.attachment_url,
|
||||
a.username AS sender
|
||||
FROM Messages m
|
||||
JOIN Accounts a ON m.user_id = a.user_id
|
||||
WHERE m.conversation_id = $1
|
||||
AND m.message_id < $2
|
||||
ORDER BY m.message_id DESC
|
||||
LIMIT $3;
|
||||
`;
|
||||
params = [conversation_id, cursor, limit];
|
||||
} else {
|
||||
query = `
|
||||
SELECT
|
||||
m.message_id,
|
||||
m.content AS message,
|
||||
m.sent_at,
|
||||
m.attachment_url,
|
||||
a.username AS sender
|
||||
FROM Messages m
|
||||
JOIN Accounts a ON m.user_id = a.user_id
|
||||
WHERE m.conversation_id = $1
|
||||
ORDER BY m.message_id DESC
|
||||
LIMIT $2;
|
||||
`;
|
||||
params = [conversation_id, limit];
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Get messages for user_id: ${user_id}, receiverUsername: ${receiverUsername}`,
|
||||
`Get messages for user_id: ${user_id}, conversation_id: ${conversation_id}`,
|
||||
);
|
||||
|
||||
const results = await client.query(query, params);
|
||||
let messages = results.rows;
|
||||
if (!cursor) {
|
||||
messages = messages.reverse();
|
||||
@@ -414,6 +320,7 @@ async function getMessages(user_id, receiverUsername, limit = 50, cursor = 0) {
|
||||
return messages;
|
||||
} catch (e) {
|
||||
console.error("Failed to get messages ", e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,9 +535,28 @@ async function getContacts(user_id) {
|
||||
c.contact_user_id AS user_id,
|
||||
a.username AS username,
|
||||
c.last_active,
|
||||
m.conversation_id,
|
||||
'direct' AS type
|
||||
FROM Contacts c
|
||||
JOIN Accounts a ON a.user_id = c.contact_user_id
|
||||
JOIN Memberships m ON m.user_id = c.contact_user_id AND m.conversation_id = (
|
||||
SELECT conversation_id
|
||||
FROM Conversations
|
||||
WHERE conversation_type = 'direct'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = Conversations.conversation_id
|
||||
AND user_id = $1
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM Memberships
|
||||
WHERE conversation_id = Conversations.conversation_id
|
||||
AND user_id = c.contact_user_id
|
||||
)
|
||||
LIMIT 1
|
||||
)
|
||||
WHERE c.user_id = $1
|
||||
ORDER BY c.last_active DESC;
|
||||
`;
|
||||
@@ -653,10 +579,10 @@ async function getContacts(user_id) {
|
||||
|
||||
const contacts = contactsResult.rows;
|
||||
const groups = groupsResult.rows.map((group) => ({
|
||||
group_id: group.id,
|
||||
username: group.username,
|
||||
last_active: group.last_active,
|
||||
type: group.type,
|
||||
conversation_id: group.id,
|
||||
}));
|
||||
|
||||
// Combine contacts and groups
|
||||
@@ -726,6 +652,24 @@ async function updateContactLastActive(userUsername, receiverUsername) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getConversationsForUser(user_id) {
|
||||
const query = `
|
||||
SELECT conversation_id
|
||||
FROM Memberships
|
||||
WHERE user_id = $1;
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await client.query(query, [user_id]);
|
||||
const conversationIds = result.rows.map((row) => row.conversation_id);
|
||||
console.log("getconversationsforuser: ", conversationIds);
|
||||
return conversationIds;
|
||||
} catch (e) {
|
||||
console.error("Failed to get conversations for user ", e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getTime() {
|
||||
return new Date();
|
||||
}
|
||||
@@ -747,4 +691,5 @@ module.exports = {
|
||||
createGroup,
|
||||
addMemberToGroup,
|
||||
addMemberToGroupByUsername,
|
||||
getConversationsForUser,
|
||||
};
|
||||
|
||||
@@ -331,6 +331,9 @@ app.post("/api/chat/groups/add", async (req, res) => {
|
||||
if (!username) {
|
||||
return res.status(400).json({ message: "Username not provided" });
|
||||
}
|
||||
if (!group_id) {
|
||||
return res.status(400).json({ message: "group_id not provided" });
|
||||
}
|
||||
const result = await addMemberToGroupByUsername(group_id, username);
|
||||
if (result !== null) {
|
||||
return res.status(200).json({ message: "Successfully added member" });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { Server } = require("socket.io");
|
||||
const { insertMessage } = require("../db/db");
|
||||
const { insertMessage, getConversationsForUser } = require("../db/db");
|
||||
const { isValidUsername } = require("../utils/filter");
|
||||
const { verifyJwtToken } = require("../auth/jwt");
|
||||
const console = require("node:console");
|
||||
@@ -62,21 +62,31 @@ function initializeSocket(io) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.join(username); // join username room
|
||||
try {
|
||||
const conversations = await getConversationsForUser(socket.user_id);
|
||||
socket.join(conversations);
|
||||
} catch (e) {
|
||||
console.error("(socket) Failed to get user conversations");
|
||||
}
|
||||
|
||||
socket.on("chat message", async (msg, callback) => {
|
||||
const { message, recipient, attachment_url } = msg;
|
||||
const sender = username;
|
||||
const sender = socket.user_id;
|
||||
|
||||
if (!message && !attachment_url) {
|
||||
callback({
|
||||
status: "error",
|
||||
tempId: msg.tempId,
|
||||
message: "No message or attachment provided",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!recipient) {
|
||||
callback({ status: "error", message: "No recipient provided" });
|
||||
callback({
|
||||
status: "error",
|
||||
tempId: msg.tempId,
|
||||
message: "No recipient provided",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,16 +106,25 @@ function initializeSocket(io) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { message_id, timestamp, sender_id } = insertedMessage;
|
||||
const {
|
||||
message_id,
|
||||
content,
|
||||
sent_at,
|
||||
sender_id,
|
||||
conversation_id,
|
||||
timestamp,
|
||||
} = insertedMessage;
|
||||
console.log("(socket) received from chat message", msg);
|
||||
|
||||
io.to(username).to(recipient).emit("chat message", {
|
||||
sender,
|
||||
message,
|
||||
message: content,
|
||||
attachment_url,
|
||||
recipient,
|
||||
message_id,
|
||||
sender_id,
|
||||
sent_at,
|
||||
conversation_id,
|
||||
});
|
||||
console.log("(socket) sent on 'chat message' socket: ", {
|
||||
sender,
|
||||
|
||||
Reference in New Issue
Block a user