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 981257c..f8ef70c 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 @@ -164,6 +165,7 @@ export const semanticQuery = async ( notes, message: "Here are all your notes.", }); + return; } else if (intent === "create_note") { const trimmedQuery = await trimCommand(query); createNoteFromBackend(trimmedQuery); @@ -185,9 +187,11 @@ export const semanticQuery = async ( intent, message: summaryMessage, }); + return; } catch (err) { console.error(err); res.status(500).json({ error: "Error performing semantic search" }); + return; } }; @@ -207,6 +211,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); @@ -236,12 +241,15 @@ export const deleteNoteById = async ( if (deleted) { res.status(200).json({ message: "Note deleted successfully" }); + return; } else { res.status(404).json({ error: "Note not found" }); + return; } } catch (err) { console.error(err); res.status(500).json({ error: "Error deleting note" }); + return; } }; @@ -270,11 +278,14 @@ export const deleteNotes = async ( res .status(200) .json({ message: `${deletedCount} note(s) deleted successfully` }); + return; } else { res.status(404).json({ error: "No notes found for deletion" }); + return; } } catch (err) { console.error(err); res.status(500).json({ error: "Error deleting notes" }); + return; } }; 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 b8005c4..1043470 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, @@ -21,4 +26,40 @@ router.delete("/:id", deleteNoteById); router.delete("/", deleteNotes); 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/components/NoteForm.tsx b/client/src/components/NoteForm.tsx index d3f1fd1..a594249 100644 --- a/client/src/components/NoteForm.tsx +++ b/client/src/components/NoteForm.tsx @@ -23,8 +23,10 @@ export default function NoteForm({ setQuery }: NoteFormProps) { handleSubmit, refreshNotes, handleSearchChange, + handleMicrophoneClick, } = useForm({ setQuery }); + //const { text, startRecording, stopRecording, isRecording } = useSpeechToText(); const { startRecording, stopRecording } = useSpeechToText(); return ( @@ -90,7 +92,7 @@ export default function NoteForm({ setQuery }: NoteFormProps) {