From 6fdec54aa4ab2ed32b810de10daf11a0c3a7b25c Mon Sep 17 00:00:00 2001 From: bexter11 Date: Tue, 11 Mar 2025 00:51:29 -0400 Subject: [PATCH] adds websocket for updating ui when note is added in backend --- backend/package-lock.json | 35 ++++++++++++++++++- backend/package.json | 4 ++- backend/src/controllers/noteController.ts | 2 ++ backend/src/db.ts | 23 ++++++++++++- backend/src/routes/noteRoutes.ts | 41 +++++++++++++++++++++++ client/src/store/useNoteStore.ts | 15 +++++++-- 6 files changed, 115 insertions(+), 5 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index bf15ada..6059149 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -16,7 +16,8 @@ "multer": "^1.4.5-lts.1", "openai": "^4.85.4", "pg": "^8.13.3", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "ws": "^8.18.1" }, "devDependencies": { "@types/cors": "^2.8.17", @@ -24,6 +25,7 @@ "@types/multer": "^1.4.12", "@types/node": "^22.13.5", "@types/pg": "^8.11.11", + "@types/ws": "^8.18.0", "nodemon": "^3.1.9", "ts-node": "^10.9.2", "typescript": "^5.7.3" @@ -247,6 +249,16 @@ "@types/send": "*" } }, + "node_modules/@types/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -2583,6 +2595,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml2js": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", diff --git a/backend/package.json b/backend/package.json index 1efdef6..f8eea89 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,7 +21,8 @@ "multer": "^1.4.5-lts.1", "openai": "^4.85.4", "pg": "^8.13.3", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "ws": "^8.18.1" }, "devDependencies": { "@types/cors": "^2.8.17", @@ -29,6 +30,7 @@ "@types/multer": "^1.4.12", "@types/node": "^22.13.5", "@types/pg": "^8.11.11", + "@types/ws": "^8.18.0", "nodemon": "^3.1.9", "ts-node": "^10.9.2", "typescript": "^5.7.3" diff --git a/backend/src/controllers/noteController.ts b/backend/src/controllers/noteController.ts index 1988b9d..a13faf3 100644 --- a/backend/src/controllers/noteController.ts +++ b/backend/src/controllers/noteController.ts @@ -7,6 +7,7 @@ import { generateQueryResponse, } from "../services/aiService"; import { generateTitle } from "../services/aiService"; +import { broadcastUpdate } from "../routes/noteRoutes"; /** * Note controller that saves a note to the postgres database @@ -206,6 +207,7 @@ export async function createNoteFromBackend(content: string) { try { const note = new Note(title, content); const savedNote = await note.save(); + await broadcastUpdate(); // Notify frontend return savedNote; } catch (error) { console.error("Error creating note:", error); diff --git a/backend/src/db.ts b/backend/src/db.ts index 9aa05df..2e745e9 100644 --- a/backend/src/db.ts +++ b/backend/src/db.ts @@ -1,4 +1,4 @@ -import { Pool } from "pg"; +/*import { Pool } from "pg"; import dotenv from "dotenv"; dotenv.config(); @@ -9,3 +9,24 @@ export const pool = new Pool({ rejectUnauthorized: false, }, }); +*/ + + +import { Pool } from "pg"; +import dotenv from "dotenv"; + +dotenv.config(); + +export const pool = new Pool({ + user: process.env.PG_USER, + host: process.env.DATABASE_URL, + password: process.env.PG_PASSWORD, + port: Number(process.env.PG_PORT), +}); + +// Table Schema currently +// CREATE TABLE notes ( +// id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, +// title VARCHAR(255), +// content VARCHAR(65535) +// ); diff --git a/backend/src/routes/noteRoutes.ts b/backend/src/routes/noteRoutes.ts index 9988678..080fd69 100644 --- a/backend/src/routes/noteRoutes.ts +++ b/backend/src/routes/noteRoutes.ts @@ -1,4 +1,9 @@ import express from "express"; +import WebSocket, { WebSocketServer } from "ws"; +import { pool } from "../db"; +import dotenv from "dotenv"; +dotenv.config(); + import { createNote, getNotes, @@ -19,4 +24,40 @@ router.get("/:id", getNoteById); router.delete("/:id", deleteNoteById); router.put("/:id", updateNote); +const wss = new WebSocketServer({ port: Number(process.env.WS_PORT) }); +const clients = new Set(); + +// When a client connects +wss.on("connection", (ws) => { + console.log("Client connected"); + clients.add(ws); + + ws.on("close", () => { + console.log("Client disconnected"); + clients.delete(ws); + }); +}); + +const fetchNotesFromDB = async () => { + try { + const result = await pool.query("SELECT * FROM notes"); + return result.rows; + } catch (error) { + console.error("Error fetching notes:", error); + return []; + } +}; + +// Broadcast updates to all connected clients +export const broadcastUpdate = async () => { + const allNotes = await fetchNotesFromDB(); // Get notes from the database + const message = JSON.stringify({ type: "update", notes: allNotes }); + + clients.forEach((client) => { + if (client.readyState === WebSocket.OPEN) { + client.send(message); + } + }); +}; + export { router as noteRoutes }; diff --git a/client/src/store/useNoteStore.ts b/client/src/store/useNoteStore.ts index 30f9e8b..982dcbd 100644 --- a/client/src/store/useNoteStore.ts +++ b/client/src/store/useNoteStore.ts @@ -8,7 +8,17 @@ import { } from "@/app/api/postgresRequests"; import { Note, NoteStore } from "@/types/note"; -export const useNoteStore = create((set) => ({ +const socket = new WebSocket("ws://localhost:8080"); + +export const useNoteStore = create((set) => { + socket.onmessage = (event) => { + const message = JSON.parse(event.data); + if (message.type === "update") { + set({ allNotes: message.notes }); + } + }; + + return { allNotes: [], aiResponse: "", deleteModalIsOpen: false, @@ -68,4 +78,5 @@ export const useNoteStore = create((set) => ({ aiResponse: result.message, }); }, -})); +}; +});