diff --git a/package-lock.json b/package-lock.json index 4b3c9ffe..0b6066eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@eslint/css": "^0.10.0", "@eslint/js": "^9.34.0", "@eslint/json": "^0.13.1", + "@types/mongoose": "^5.11.96", + "@types/node": "^24.10.1", "concurrently": "^9.2.0", "eslint": "^9.34.0", "eslint-plugin-react": "^7.37.5", @@ -308,6 +310,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", + "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -322,6 +334,43 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mongoose": { + "version": "5.11.96", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.96.tgz", + "integrity": "sha512-keiY22ljJtXyM7osgScmZOHV6eL5VFUD5tQumlu+hjS++HND5nM8jNEdj5CSWfKIJpVwQfPuwQ2SfBqUnCAVRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mongoose": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -609,6 +658,16 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2404,6 +2463,16 @@ "node": ">=4.0" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2544,6 +2613,13 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "dev": true, + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -2584,6 +2660,110 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.0.tgz", + "integrity": "sha512-SxqNb8yx+VOjIOx2l7HqkGvYuLC/T85d+jPvqGDdUbKJFz/5PVSsVxQzypQsX7chenYvq5bd8jIr4LtunedE7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3276,6 +3456,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -3329,6 +3516,16 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -3539,6 +3736,19 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -3666,6 +3876,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3676,6 +3893,30 @@ "punycode": "^2.1.0" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index b04731fc..2dfcb9c4 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "@eslint/css": "^0.10.0", "@eslint/js": "^9.34.0", "@eslint/json": "^0.13.1", + "@types/mongoose": "^5.11.96", + "@types/node": "^24.10.1", "concurrently": "^9.2.0", "eslint": "^9.34.0", "eslint-plugin-react": "^7.37.5", diff --git a/server/package-lock.json b/server/package-lock.json index 47524e38..775bc17f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -30,6 +30,7 @@ "winston": "^3.18.3" }, "devDependencies": { + "@types/express": "^5.0.5", "nodemon": "^3.1.10" }, "engines": { @@ -102,6 +103,66 @@ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", "license": "MIT" }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.18.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.13.tgz", @@ -111,6 +172,53 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", diff --git a/server/package.json b/server/package.json index b5e43771..38198717 100644 --- a/server/package.json +++ b/server/package.json @@ -47,6 +47,7 @@ "winston": "^3.18.3" }, "devDependencies": { + "@types/express": "^5.0.5", "nodemon": "^3.1.10" } } diff --git a/server/src/controllers/noteController.ts b/server/src/controllers/noteController.ts new file mode 100644 index 00000000..2c2dfbdd --- /dev/null +++ b/server/src/controllers/noteController.ts @@ -0,0 +1,121 @@ +import { Request, Response } from 'express'; +import { NoteModel, INote } from '../models/NoteModel'; +import mongoose from 'mongoose'; + +// --- Helper function to get authenticated user ID --- +// NOTE: This assumes an auth middleware attaches the user ID to the request object. +const getUserId = (req: Request): mongoose.Types.ObjectId | undefined => { + // ADJUST THIS LINE if req.user.id is named differently in the project! + return (req as any).user?.id ? new mongoose.Types.ObjectId((req as any).user.id) : undefined; +}; + +// --- 1. GET: Fetch all private notes for a specific project by the authenticated user --- +export const getProjectNotes = async (req: Request, res: Response): Promise => { + try { + const userId = getUserId(req); + if (!userId) { + res.status(401).json({ message: 'User not authenticated.' }); + return; + } + + const { projectId } = req.params; + + const notes: INote[] = await NoteModel.find({ + projectId: projectId, + userId: userId, // CRUCIAL: Only fetch notes belonging to this user + }).sort({ createdAt: -1 }); + + res.status(200).json(notes); + } catch (error) { + console.error('Error fetching notes:', error); + res.status(500).json({ message: 'Server error while fetching notes.' }); + } +}; + +// --- 2. POST: Create a new private note --- +export const createNote = async (req: Request, res: Response): Promise => { + try { + const userId = getUserId(req); + if (!userId) { + res.status(401).json({ message: 'User not authenticated.' }); + return; + } + + const { projectId, content, status } = req.body; + + if (!projectId || !content) { + res.status(400).json({ message: 'Project ID and content are required.' }); + return; + } + + const newNote: INote = await NoteModel.create({ + userId: userId, // Automatically link to the current user + projectId, + content, + status: status || 'todo', + }); + + res.status(201).json(newNote); + } catch (error) { + console.error('Error creating note:', error); + res.status(500).json({ message: 'Server error while creating note.' }); + } +}; + +// --- 3. PUT: Update an existing private note --- +export const updateNote = async (req: Request, res: Response): Promise => { + try { + const userId = getUserId(req); + if (!userId) { + res.status(401).json({ message: 'User not authenticated.' }); + return; + } + + const { noteId } = req.params; + const { content, status } = req.body; + + const updatedNote: INote | null = await NoteModel.findOneAndUpdate( + { _id: noteId, userId: userId }, // CRUCIAL: Find by ID AND User ID for ownership check + { content, status }, + { new: true, runValidators: true } + ); + + if (!updatedNote) { + res.status(404).json({ message: 'Note not found or you do not have permission to update it.' }); + return; + } + + res.status(200).json(updatedNote); + } catch (error) { + console.error('Error updating note:', error); + res.status(500).json({ message: 'Server error while updating note.' }); + } +}; + +// --- 4. DELETE: Delete a private note --- +export const deleteNote = async (req: Request, res: Response): Promise => { + try { + const userId = getUserId(req); + if (!userId) { + res.status(401).json({ message: 'User not authenticated.' }); + return; + } + + const { noteId } = req.params; + + const deletedNote: INote | null = await NoteModel.findOneAndDelete({ + _id: noteId, + userId: userId, // CRUCIAL: Find by ID AND User ID for ownership check + }); + + if (!deletedNote) { + res.status(404).json({ message: 'Note not found or you do not have permission to delete it.' }); + return; + } + + res.status(200).json({ message: 'Note successfully deleted.' }); + } catch (error) { + console.error('Error deleting note:', error); + res.status(500).json({ message: 'Server error while deleting note.' }); + } +}; \ No newline at end of file diff --git a/server/src/models/NoteModel.ts b/server/src/models/NoteModel.ts new file mode 100644 index 00000000..67444c5d --- /dev/null +++ b/server/src/models/NoteModel.ts @@ -0,0 +1,47 @@ +import mongoose, { Document, Schema, Model } from 'mongoose'; + +// 1. Define the TypeScript Interface for the Note Document +export interface INote extends Document { + userId: mongoose.Types.ObjectId; + projectId: mongoose.Types.ObjectId; + content: string; + status: 'todo' | 'in-progress' | 'done'; // Added status for progress tracking +} + +// 2. Define the Mongoose Schema +const NoteSchema: Schema = new Schema( + { + // Links the note to the User who created it (Privacy check) + userId: { + type: Schema.Types.ObjectId, + ref: 'User', // Assuming the User model is named 'User' + required: true, + index: true, + }, + // Links the note to the Project it is related to + projectId: { + type: Schema.Types.ObjectId, + ref: 'Project', // Assuming the Project model is named 'Project' + required: true, + index: true, + }, + // The main content of the private note + content: { + type: String, + required: true, + trim: true, + }, + // Status for tracking progress + status: { + type: String, + enum: ['todo', 'in-progress', 'done'], + default: 'todo', + }, + }, + { + timestamps: true, // Adds createdAt and updatedAt fields + } +); + +// 3. Export the Mongoose Model +export const NoteModel: Model = mongoose.model('Note', NoteSchema); \ No newline at end of file diff --git a/server/src/routes/noteRoutes.ts b/server/src/routes/noteRoutes.ts new file mode 100644 index 00000000..3a2db335 --- /dev/null +++ b/server/src/routes/noteRoutes.ts @@ -0,0 +1,27 @@ +import { Router } from 'express'; +import { + getProjectNotes, + createNote, + updateNote, + deleteNote, +} from '../controllers/noteController'; +// NOTE: Update the path if your authMiddleware is located elsewhere +import authMiddleware from '../middlewares/auth.middleware'; +const router: Router = Router(); + +// All note routes require authentication to ensure privacy +router.use(authMiddleware); + +// GET /api/v1/notes/:projectId -> Fetch all notes for a specific project by the user +router.get('/:projectId', getProjectNotes); + +// POST /api/v1/notes -> Create a new note +router.post('/', createNote); + +// PUT /api/v1/notes/:noteId -> Update a specific note +router.put('/:noteId', updateNote); + +// DELETE /api/v1/notes/:noteId -> Delete a specific note +router.delete('/:noteId', deleteNote); + +export default router; \ No newline at end of file diff --git a/server/src/server.js b/server/src/server.js index 31650e16..aa7863ab 100644 --- a/server/src/server.js +++ b/server/src/server.js @@ -14,6 +14,7 @@ import sanitizeInput from './middlewares/sanitize.middleware.js'; // Routes import monitorRoutes from './routes/api/monitor.routes.js'; import router from './routes/index.js'; +import noteRoutes from './routes/noteRoutes.js'; // <--- NEW IMPORT dotenv.config(); @@ -51,6 +52,11 @@ server.use('/monitor', monitorRoutes); // API Routes server.use('/api', router); +// ADDED: Mount the new Note Routes under the /api/v1/notes path +// We use the '/api' prefix here, but your noteRoutes file uses /:projectId etc., +// so the final path will be /api/notes/:projectId +server.use('/api/notes', noteRoutes); // <--- NEW MOUNT + // Error handler (last middleware) server.use(errorHandler);