diff --git a/client/src/api/contactsApi.tsx b/client/src/api/contactsApi.tsx index 8787c4b..451fa42 100644 --- a/client/src/api/contactsApi.tsx +++ b/client/src/api/contactsApi.tsx @@ -13,7 +13,7 @@ export async function getContactsList(): Promise { } } -export async function setContactStatus(conversation_id: number, read: boolean) { +export async function setContactStatus(conversation_id: string, read: boolean) { try { const response = await axiosClient.put( `/api/chat/contacts/${conversation_id}`, @@ -40,7 +40,7 @@ export async function sendContact(contact: string) { } export async function getMessages( - contact: number | null, + contact: string | null, cursor: number | null = 0, limit: number = 50, ): Promise<{ messages: ChatMessages[] }> { diff --git a/client/src/components/chat/ContactsList.tsx b/client/src/components/chat/ContactsList.tsx index 4bfc488..8063a21 100644 --- a/client/src/components/chat/ContactsList.tsx +++ b/client/src/components/chat/ContactsList.tsx @@ -48,8 +48,8 @@ function ContactsList({ 'added to group', (msg: { username: string; - user_id: number; - group_id: number; + user_id: string; + group_id: string; isadmin: false; }) => { console.log('added to group, fetching contacts'); @@ -57,10 +57,6 @@ function ContactsList({ fetchContacts(); }, ); - - return () => { - socket?.off('added to group'); - }; }, []); const fetchContacts = async () => { @@ -70,7 +66,7 @@ function ContactsList({ setContactsList(contacts); }; - async function removeContact(contactId: number, conversation_id: number) { + async function removeContact(contactId: number, conversation_id: string) { try { console.log('delete contact, conversation_id: ', conversation_id); diff --git a/client/src/components/chat/MessagesArea.tsx b/client/src/components/chat/MessagesArea.tsx index 22c018a..61324ea 100644 --- a/client/src/components/chat/MessagesArea.tsx +++ b/client/src/components/chat/MessagesArea.tsx @@ -16,7 +16,7 @@ type MessagesAreaProps = { updateContactStatus: (contact: ContactsProps, read: boolean) => void; messageHandler: (msg: ChatMessages) => void; setContactsList: React.Dispatch>; - fetchPreviousMessages: (contact: number | null) => Promise; + fetchPreviousMessages: (contact: string | null) => Promise; errorMessage: string | null; contactsList: ContactsProps[]; }; @@ -194,7 +194,7 @@ function MessagesArea({ socket.on( 'delete message', - (msg: { conversation_id: number; message_id: number }) => { + (msg: { conversation_id: string; message_id: number }) => { console.log('deleted message from state'); setMessages((prevMessages) => prevMessages.filter( diff --git a/client/src/components/chat/ParticipantsBar.tsx b/client/src/components/chat/ParticipantsBar.tsx index abe3530..ccd9f9a 100644 --- a/client/src/components/chat/ParticipantsBar.tsx +++ b/client/src/components/chat/ParticipantsBar.tsx @@ -14,7 +14,7 @@ import { useOutletContext } from 'react-router-dom'; import LoadingWheel from '@/components/chat/LoadingWheel.tsx'; type ParticipantsProps = { - user_id: number; + user_id: string; username: string; isadmin: boolean; }; @@ -75,8 +75,8 @@ function ParticipantsBar({ 'added to group', (msg: { username: string; - user_id: number; - group_id: number; + user_id: string; + group_id: string; isadmin: false; }) => { const { group_id } = msg; @@ -112,7 +112,7 @@ function ParticipantsBar({ }, ); - socket.on('left group', (msg: { user_id: number; group_id: number }) => { + socket.on('left group', (msg: { user_id: string; group_id: string }) => { console.log(`(socket on "left group")`, msg); // Initialize contact again to clear messages and participants list if ( @@ -128,10 +128,11 @@ function ParticipantsBar({ return () => { socket?.off('left group'); + socket?.off('added to group'); }; }, [contact, participants]); - const handleRemoveUser = async (userId: number) => { + const handleRemoveUser = async (userId: string) => { try { setIsLoading(true); socket?.emit( diff --git a/client/src/components/chat/UserProfile.tsx b/client/src/components/chat/UserProfile.tsx index cbe5339..4818aac 100644 --- a/client/src/components/chat/UserProfile.tsx +++ b/client/src/components/chat/UserProfile.tsx @@ -24,9 +24,7 @@ function UserProfile() {
-

- {user?.user_id} {user.username} -

+

{user.username}

diff --git a/client/src/pages/Chat.tsx b/client/src/pages/Chat.tsx index 2e55983..5be5059 100644 --- a/client/src/pages/Chat.tsx +++ b/client/src/pages/Chat.tsx @@ -14,22 +14,22 @@ import ParticipantsBar from '@/components/chat/ParticipantsBar.tsx'; export type ChatMessages = { sender: string; message: string; - recipient: number; // conversation_id + recipient: string; // conversation_id message_id: number; pending: boolean; attachment_urls: string[] | null; - sender_id: number; - conversation_id: number; + sender_id: string; + conversation_id: string; sent_at: Date; }; export type ContactsProps = { read: boolean; username: string; - user_id: number; + user_id: string; id: number; type: 'direct' | 'group'; - conversation_id: number; + conversation_id: string; last_active: string; }; @@ -92,7 +92,7 @@ function Chat() { } } - const fetchMessages = async (conversation_id: number) => { + const fetchMessages = async (conversation_id: string) => { console.log('Fetching messages for: ', conversation_id); try { const messages = await getMessages(conversation_id); @@ -115,7 +115,7 @@ function Chat() { } }; - const fetchPreviousMessages = async (contact: number | null) => { + const fetchPreviousMessages = async (contact: string | null) => { if (!hasMoreMessages) { return; } diff --git a/client/src/socket/socket.tsx b/client/src/socket/socket.tsx index 3b5e7a4..7912856 100644 --- a/client/src/socket/socket.tsx +++ b/client/src/socket/socket.tsx @@ -22,7 +22,7 @@ function initializeSocket(token: string): Socket | null { } async function joinRoom( - conversation_id: number, + conversation_id: string, ): Promise<{ success: boolean; message: string }> { console.log('Attempting to join room:', conversation_id); diff --git a/client/src/utils/ProtectedRoutes.tsx b/client/src/utils/ProtectedRoutes.tsx index bc3dcc3..d33892c 100644 --- a/client/src/utils/ProtectedRoutes.tsx +++ b/client/src/utils/ProtectedRoutes.tsx @@ -7,7 +7,7 @@ import { axiosClient } from '../App.tsx'; export type UsernameType = { username: string | null; - user_id: number | null; + user_id: string | null; }; function ProtectedRoutes() { diff --git a/server/db/db.js b/server/db/db.js index 5ba0a16..9a2be3c 100644 --- a/server/db/db.js +++ b/server/db/db.js @@ -33,10 +33,16 @@ client // Creating database tables async function createTables() { try { - // Create Accounts Table + // Enable UUID extension + await client.query(` + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + `); + console.log("Successfully enabled UUID extension"); + + // Create Accounts Table with UUID await client.query(` CREATE TABLE IF NOT EXISTS Accounts ( - user_id SERIAL PRIMARY KEY, + user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() @@ -44,16 +50,11 @@ async function createTables() { CREATE INDEX IF NOT EXISTS idx_username ON Accounts (username); `); console.log("Successfully created Accounts table"); - } catch (e) { - console.error("Failed to create Accounts table: ", e); - throw e; - } - try { - // Create Conversations Table + // Create Conversations Table with UUID await client.query(` CREATE TABLE IF NOT EXISTS Conversations ( - conversation_id SERIAL PRIMARY KEY, + conversation_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), conversation_type VARCHAR(10) NOT NULL CHECK (conversation_type IN ('direct', 'group')), name VARCHAR(255), created_at TIMESTAMPTZ DEFAULT NOW(), @@ -62,18 +63,13 @@ async function createTables() { CREATE INDEX IF NOT EXISTS idx_conversation_type ON Conversations (conversation_type); `); console.log("Successfully created Conversations table"); - } catch (e) { - console.error("Failed to create Conversations table: ", e); - throw e; - } - try { - // Create Messages Table + // Create Messages Table with SERIAL but referencing UUID await client.query(` CREATE TABLE IF NOT EXISTS Messages ( message_id SERIAL PRIMARY KEY, - conversation_id INT REFERENCES Conversations(conversation_id) ON DELETE CASCADE, - user_id INT REFERENCES Accounts(user_id) ON DELETE CASCADE, + conversation_id UUID REFERENCES Conversations(conversation_id) ON DELETE CASCADE, + user_id UUID REFERENCES Accounts(user_id) ON DELETE CASCADE, content TEXT NOT NULL, attachment_urls TEXT[], sent_at TIMESTAMPTZ DEFAULT NOW() @@ -83,17 +79,12 @@ async function createTables() { CREATE INDEX IF NOT EXISTS idx_messages_conversation_sent_at ON Messages (conversation_id, sent_at); `); console.log("Successfully created Messages table"); - } catch (e) { - console.error("Failed to create Messages table: ", e); - throw e; - } - try { - // Create Memberships Table + // Create Memberships Table referencing UUID await client.query(` CREATE TABLE IF NOT EXISTS Memberships ( - conversation_id INT NOT NULL REFERENCES Conversations(conversation_id) ON DELETE CASCADE, - user_id INT REFERENCES Accounts(user_id) ON DELETE CASCADE, + conversation_id UUID NOT NULL REFERENCES Conversations(conversation_id) ON DELETE CASCADE, + user_id UUID REFERENCES Accounts(user_id) ON DELETE CASCADE, joined_at TIMESTAMPTZ DEFAULT NOW(), PRIMARY KEY (conversation_id, user_id) ); @@ -102,18 +93,13 @@ async function createTables() { CREATE INDEX IF NOT EXISTS idx_memberships_conversation_joined_at ON Memberships (conversation_id, joined_at); `); console.log("Successfully created Memberships table"); - } catch (e) { - console.error("Failed to create Memberships table: ", e); - throw e; - } - try { - // Create Contacts Table + // Create Contacts Table with SERIAL but referencing UUID await client.query(` CREATE TABLE IF NOT EXISTS Contacts ( contact_id SERIAL PRIMARY KEY, - user_id INT REFERENCES Accounts(user_id) ON DELETE CASCADE, - conversation_id INT NOT NULL REFERENCES Conversations(conversation_id) ON DELETE CASCADE, + user_id UUID REFERENCES Accounts(user_id) ON DELETE CASCADE, + conversation_id UUID NOT NULL REFERENCES Conversations(conversation_id) ON DELETE CASCADE, read BOOLEAN NOT NULL DEFAULT FALSE, last_active TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT unique_contact UNIQUE (user_id, conversation_id) @@ -123,19 +109,14 @@ async function createTables() { CREATE INDEX IF NOT EXISTS idx_contacts_contact_id ON Contacts (contact_id); `); console.log("Successfully created Contacts table"); - } catch (e) { - console.error("Failed to create Contacts table: ", e); - throw e; - } - try { - // Create GroupAdmins Table with Trigger + // Create GroupAdmins Table with Trigger, referencing UUID await client.query(` -- Create the base table CREATE TABLE IF NOT EXISTS GroupAdmins ( - conversation_id INT NOT NULL REFERENCES Conversations(conversation_id) ON DELETE CASCADE, - user_id INT NOT NULL REFERENCES Accounts(user_id) ON DELETE CASCADE, - granted_by INT NOT NULL REFERENCES Accounts(user_id) ON DELETE CASCADE, + conversation_id UUID NOT NULL REFERENCES Conversations(conversation_id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES Accounts(user_id) ON DELETE CASCADE, + granted_by UUID NOT NULL REFERENCES Accounts(user_id) ON DELETE CASCADE, granted_at TIMESTAMPTZ DEFAULT NOW(), PRIMARY KEY (conversation_id, user_id) ); @@ -181,7 +162,7 @@ async function createTables() { `); console.log("Successfully created GroupAdmins table with trigger"); } catch (e) { - console.error("Failed to create GroupAdmins table: ", e); + console.error("Failed to create tables: ", e); throw e; } diff --git a/server/package-lock.json b/server/package-lock.json index 4a4c565..9c2a512 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,7 +13,6 @@ "express": "^4.21.0", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", - "nanoid": "^5.0.7", "pg": "^8.13.0", "socket.io": "^4.8.0", "uuid": "^10.0.0" @@ -436,36 +435,27 @@ } }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -602,9 +592,9 @@ } }, "node_modules/engine.io": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.1.tgz", - "integrity": "sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", @@ -612,7 +602,7 @@ "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -668,9 +658,9 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -678,7 +668,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -692,7 +682,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -707,12 +697,16 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1408,24 +1402,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/nanoid": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", - "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1589,9 +1565,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/pg": { diff --git a/server/package.json b/server/package.json index 90cbe5b..bcc3756 100644 --- a/server/package.json +++ b/server/package.json @@ -12,7 +12,6 @@ "express": "^4.21.0", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", - "nanoid": "^5.0.7", "pg": "^8.13.0", "socket.io": "^4.8.0", "uuid": "^10.0.0" diff --git a/server/server.js b/server/server.js index 172e661..d627f4d 100644 --- a/server/server.js +++ b/server/server.js @@ -362,7 +362,7 @@ app.post("/api/chat/groups/create", authorizeUser, async (req, res) => { return res.status(500).json({ message: "Failed to create group" }); } console.log("Successfully created group: ", groupname, "id: ", group_id); - console.log("io.to: ", contact_user_id, "added to group"); + console.log(`io.to: ${contact_user_id} added to group`); io.to(contact_user_id).emit("added to group", { group_id }); return res.status(200).json({ message: `Successfully created group: ${groupname}`,