From c1e33ba6413f9a879275bd2ac52a66957bbd6d83 Mon Sep 17 00:00:00 2001 From: Idahel Date: Thu, 12 Jun 2025 08:19:36 +0200 Subject: [PATCH 1/8] added post, delete and patch --- package.json | 1 + server.js | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bf25bb6..00addae 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", "express": "^4.17.3", + "express-list-endpoints": "^7.1.1", "nodemon": "^3.0.1" } } diff --git a/server.js b/server.js index f47771b..b90d8c6 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,13 @@ import cors from "cors" import express from "express" +import listEndpoints from 'express-list-endpoints' +import mongoose from "mongoose" + + +import thoughtData from "./data.json" + +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happy-thoughts" +mongoose.connect(mongoUrl) // Defines the port the app will run on. Defaults to 8080, but can be overridden // when starting the server. Example command to overwrite PORT env variable value: @@ -11,12 +19,97 @@ const app = express() app.use(cors()) app.use(express.json()) +const thoughtSchema = new mongoose.Schema({ + +}) + +const Thought = mongoose.model("thought", thoughtSchema) + +if (process.env.RESET_DB){ + const seedDatabase = async () => { + await Thought.deleteMany({}) + thoughtData.forEach(thought => { + new Thought(thought).save() + }) +} +seedDatabase() +} + // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!") + const endpoints = listEndpoints(app) + + res.json ({ + message: "Welcome to the Happy Thoughts API", + endpoints: endpoints + }) +}) + +app.get("/thoughts", async (req, res) => { + + const { hearts, message, page, limit, sort } = req.query + + const query = {} + + let filteredThoughts = [...thoughtData] + +//Filter to get the messages with at least the amount of hearts that the user asks for + if (hearts) { + const minHearts = parseInt(hearts, 10) + if(!isNaN(minHearts)) { + filteredThoughts = filteredThoughts.filter(thought => thought.hearts >= minHearts) + } else { + return res.status(400).json({error:"Invalid hearts parameter. Must be a number"}) + } +} + +//Filtering by message or part of message content eg if the user search for "happy" + if (message) { + const searchMessage = message.toLowerCase(); + filteredThoughts = filteredThoughts.filter(thought => + thought.message.toLowerCase().includes(searchMessage) + ) + } + +//Sort the messages for date created and amount of hearts + if (sort) { + if (sort === 'createdAt') { + filteredThoughts.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); // Newest first + } else if (sort === 'hearts') { + filteredThoughts.sort((a, b) => b.hearts - a.hearts); // Most hearts first + } else { + return res.status(400).json({ error: "Invalid 'sort' parameter. Valid options are 'createdAt' or 'hearts'." }) + } + } + +//Let the user choose to view a specific amount of thoughts per page and also to go between pages + const pageNum = parseInt(page, 10) || 1 //Default to page 1 + const limitNum = parseInt(limit, 10) || 10 //Default limit of 10 thoughts per page + const startIndex = (pageNum - 1) * limitNum + const endIndex = pageNum * limitNum + + const paginatedThoughts = filteredThoughts.slice(startIndex, endIndex) + + res.json({ + totalResults: filteredThoughts.length, + currentPage: pageNum, + resultsPerPage: paginatedThoughts.length, + thoughts: paginatedThoughts + }) +}) + +//endpoint for getting one specific thought - based on id +app.get("/thoughts/:id/", (req, res) => { + const thought = thoughtData.find((thought) => thought._id === req.params.id) + + if (!thought) { + return res.status(404).json({error: 'thought not found'}) + } + + res.json(thought) }) // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`) -}) +}) \ No newline at end of file From d796cd4d6c67076d31b3791a3cd90b26654edf00 Mon Sep 17 00:00:00 2001 From: Idahel Date: Thu, 12 Jun 2025 09:18:48 +0200 Subject: [PATCH 2/8] changed port due to errors when deploying --- package.json | 1 + server.js | 252 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 207 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 00addae..84d5f9f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "cors": "^2.8.5", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", + "mongoose": "^8.15.1", "nodemon": "^3.0.1" } } diff --git a/server.js b/server.js index b90d8c6..502cbc7 100644 --- a/server.js +++ b/server.js @@ -1,18 +1,18 @@ import cors from "cors" -import express from "express" +import express, { response } from "express" import listEndpoints from 'express-list-endpoints' import mongoose from "mongoose" import thoughtData from "./data.json" -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happy-thoughts" +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts" mongoose.connect(mongoUrl) // Defines the port the app will run on. Defaults to 8080, but can be overridden // when starting the server. Example command to overwrite PORT env variable value: // PORT=9000 npm start -const port = process.env.PORT || 8080 +const port = process.env.PORT || 9000 const app = express() // Add middlewares to enable cors and json body parsing @@ -20,31 +20,65 @@ app.use(cors()) app.use(express.json()) const thoughtSchema = new mongoose.Schema({ - + id: Number, + message: { + type: String, + required: true, + minlength: 5, + maxlength: 140, + trim: true + }, + hearts: { + type: Number, + default: 0 + }, + createdAt: { + type: Date, + default: () => new Date() + } }) -const Thought = mongoose.model("thought", thoughtSchema) +const Thought = mongoose.model("Thought", thoughtSchema) if (process.env.RESET_DB){ const seedDatabase = async () => { - await Thought.deleteMany({}) - thoughtData.forEach(thought => { - new Thought(thought).save() - }) -} -seedDatabase() + try { + await Thought.deleteMany({}) + + for (const thought of thoughtData) { + + const newThought = new Thought({ + message: thought.message, + hearts: thought.hearts || 0, + createdAt: thought.createdAt ? new Date(thought.createdAt) : new Date() + }) + await newThought.save() + } + } catch (error) { + console.error("Error seeding database:", error) + } + } + seedDatabase() } -// Start defining your routes here +// Root endpoint that provides API information + app.get("/", (req, res) => { + const endpoints = listEndpoints(app) - res.json ({ - message: "Welcome to the Happy Thoughts API", - endpoints: endpoints + res.status(200).json ({ + success: true, + response: { + message: "Welcome to the Happy Thoughts API", + endpoints: endpoints + }, + message: "API information retrieved successfully." }) }) +//Get all thoughts with filtering, sorting and pagination + app.get("/thoughts", async (req, res) => { const { hearts, message, page, limit, sort } = req.query @@ -54,59 +88,185 @@ app.get("/thoughts", async (req, res) => { let filteredThoughts = [...thoughtData] //Filter to get the messages with at least the amount of hearts that the user asks for - if (hearts) { - const minHearts = parseInt(hearts, 10) - if(!isNaN(minHearts)) { - filteredThoughts = filteredThoughts.filter(thought => thought.hearts >= minHearts) +if (hearts) { + const minHearts = parseInt(hearts, 10) + if(!isNaN(minHearts)) { + query.hearts = { $gte: minHearts } //$gte = MongoDB query operator "greater than or equal to" } else { - return res.status(400).json({error:"Invalid hearts parameter. Must be a number"}) + return res.status(400).json({ + success: false, + response: null, + message: "Invalid 'hearts' parameter. Must be a number." + }) } } //Filtering by message or part of message content eg if the user search for "happy" - if (message) { - const searchMessage = message.toLowerCase(); - filteredThoughts = filteredThoughts.filter(thought => - thought.message.toLowerCase().includes(searchMessage) - ) + if (message) { + query.message = { $regex: new RegExp(message, 'i') } } //Sort the messages for date created and amount of hearts - if (sort) { - if (sort === 'createdAt') { - filteredThoughts.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); // Newest first - } else if (sort === 'hearts') { - filteredThoughts.sort((a, b) => b.hearts - a.hearts); // Most hearts first - } else { - return res.status(400).json({ error: "Invalid 'sort' parameter. Valid options are 'createdAt' or 'hearts'." }) - } +const sortOptions = {} +if (sort) { + if (sort === 'createdAt') { + sortOptions.createdAt = -1; // Newest first + } else if (sort === 'hearts') { + sortOptions.hearts = -1; // Most hearts first + } else { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid 'sort' parameter. Valid options are 'createdAt' or 'hearts'." + }) } +} //Let the user choose to view a specific amount of thoughts per page and also to go between pages const pageNum = parseInt(page, 10) || 1 //Default to page 1 const limitNum = parseInt(limit, 10) || 10 //Default limit of 10 thoughts per page const startIndex = (pageNum - 1) * limitNum - const endIndex = pageNum * limitNum - const paginatedThoughts = filteredThoughts.slice(startIndex, endIndex) + try { + const totalResults = await Thought.countDocuments(query) + const thoughts = await Thought.find(query) + .sort(sortOptions) + .skip(startIndex) + .limit(limitNum) - res.json({ - totalResults: filteredThoughts.length, - currentPage: pageNum, - resultsPerPage: paginatedThoughts.length, - thoughts: paginatedThoughts - }) + res.status(200).json({ + success: true, + response: { + totalResults: totalResults, + currentPage: pageNum, + resultsPerPage: thoughts.length, + thoughts: thoughts + }, + message: "Thoughts retrieved successfully." + }) + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Failed to retrieve thoughts." + }) + } }) //endpoint for getting one specific thought - based on id -app.get("/thoughts/:id/", (req, res) => { - const thought = thoughtData.find((thought) => thought._id === req.params.id) +app.get("/thoughts/:id/", async (req, res) => { + + const { id } = req.params + + try { + const thought = await Thought.findById(id) + + if (!thought) { + return res.status(404).json({ + success: false, + response: null, + message: `Thought with id '${id}' not found.` + }) + } + + res.status(200).json({ + success: true, + response: thought, + message: `Thought with id '${id}' retrieved successfully.` + }) + + } catch (error) { + res.status(400).json({ + success: false, + response: error, + message: "Invalid thought ID format." + }) + } +}) + +//Post endpoint + +app.post("/thoughts", async (req, res) => { + + const {message} = req.body + + try { + const newThought = await new Thought ({message}).save() + + res.status(201).json({ + success: true, + response: newThought, + message: "Thought created successfully" + }) + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Failed to create thought." + }) + } +}) + +//Delete endpoint: delete a thought by ID + +app.delete("/thoughts/:id", async (req, res) => { - if (!thought) { - return res.status(404).json({error: 'thought not found'}) + const { id } = req.params + + try { + const deletedThought = await Thought.findByIdAndDelete(id) + + if(!deletedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought could not be found, can't delete." + }) + } + res.status(200).json({ + success: true, + response: deletedThought, + message: "Thought was successfully deleted." + }) + } catch { + res.status(500).json({ + success: false, + response: error, + message: "Failed to delete thought." + }) } +}) + +// Patch endpoint: update a thought by ID + +app.patch("/thoughts/:id", async (req, res) => { - res.json(thought) + const { id } = req.params + const { newMessage } = req.body + + try { + const updatedThought = await Thought.findByIdAndUpdate ( id, { message: newMessage }, {new: true, runValidators: true}) + + if(!updatedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought could not be found." + }) + } + + res.status(200).json({ + success: true, + response: updatedThought, + message: "Thought was successfully updated" + }) + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Could not edit thought." + }) + } }) // Start the server From 3014b187df3da6c4ab3a861910c6e4fa25064e52 Mon Sep 17 00:00:00 2001 From: Idahel Date: Thu, 12 Jun 2025 11:14:06 +0200 Subject: [PATCH 3/8] added patch to like and unlike --- server.js | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/server.js b/server.js index 502cbc7..c76c2dc 100644 --- a/server.js +++ b/server.js @@ -269,6 +269,107 @@ app.patch("/thoughts/:id", async (req, res) => { } }) +app.patch("/thoughts/:id/like", async (req, res) => { + const { id } = req.params + + try { + // Find the thought and increment its 'hearts' field by 1 + const updatedThought = await Thought.findByIdAndUpdate( + id, + { $inc: { hearts: 1 } }, // MongoDB operator to increment a field + { new: true } // Return the updated document + ) + + if (!updatedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought could not be found to like." + }) + } + + res.status(200).json({ + success: true, + response: updatedThought, + message: "Thought successfully liked." + }) + + } catch (error) { + res.status(400).json({ // Use 400 for bad ID format, 500 for other server errors + success: false, + response: error, + message: "Failed to like thought due to invalid ID or server error." + }) + } +}) + +// Patch endpoint: update a thought by ID (for message and unlike) +// This endpoint specifically handles message updates and unliking. +app.patch("/thoughts/:id", async (req, res) => { + const { id } = req.params + const { message, unlike } = req.body + + try { + const thought = await Thought.findById(id) + + if (!thought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought could not be found." + }) + } + + const updateFields = {} + if (message !== undefined) { + updateFields.message = message + } + + let newHearts = thought.hearts + if (unlike) { + newHearts = Math.max(0, thought.hearts - 1) + } + + if (newHearts !== thought.hearts || message !== undefined) { + updateFields.hearts = newHearts + } + + const updatedThought = await Thought.findByIdAndUpdate( + id, + updateFields, + { new: true, runValidators: true } + ) + + if(!updatedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought could not be found after update attempt." + }) + } + + res.status(200).json({ + success: true, + response: updatedThought, + message: "Thought was successfully updated." + }) + } catch (error) { + if (error.name === 'ValidationError') { + res.status(400).json({ + success: false, + response: error.errors, + message: "Validation failed for thought update." + }) + } else { + res.status(500).json({ + success: false, + response: error, + message: "Could not edit thought." + }) + } + } +}) + // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`) From 6b50324819f49e4d59330437934ad8ac7cc778c1 Mon Sep 17 00:00:00 2001 From: Idahel Date: Thu, 12 Jun 2025 13:15:43 +0200 Subject: [PATCH 4/8] added authentication --- package.json | 4 +- server.js | 221 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 192 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 84d5f9f..5a1642b 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,12 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", "mongoose": "^8.15.1", - "nodemon": "^3.0.1" + "nodemon": "^3.0.1", + "save": "^2.9.0" } } diff --git a/server.js b/server.js index c76c2dc..8446ea1 100644 --- a/server.js +++ b/server.js @@ -2,12 +2,14 @@ import cors from "cors" import express, { response } from "express" import listEndpoints from 'express-list-endpoints' import mongoose from "mongoose" - +import crypto from "crypto" +import bcrypt from "bcrypt" import thoughtData from "./data.json" const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts" mongoose.connect(mongoUrl) +mongoose.Promise = Promise // Defines the port the app will run on. Defaults to 8080, but can be overridden // when starting the server. Example command to overwrite PORT env variable value: @@ -19,8 +21,123 @@ const app = express() app.use(cors()) app.use(express.json()) + +// Authentication +const { Schema, model } = mongoose + +const userSchema = new Schema({ + name: { + type: String, + unique: true, + required: true + }, + email: { + type: String, + unique: true, + required: true + }, + password: { + type: String, + required: true + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex") + } +}) + +const User = model("User", userSchema) + +// Middleware for authenticating users via accessToken +const authenticateUser = async (req, res, next) => { + const accessToken = req.header("Authorization") + try { + const user = await User.findOne({ accessToken: accessToken }) + + if (user) { + req.user = user + next() + } else { + res.status(401).json({ + success: false, + response: null, + message: "Unauthorized: Access token invalid or missing." + }) + } + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Server error during authentication." + }) + } +} + +app.post("/users", (req, res) => { + try { + const { name, email, password } = req.body + const salt = bcrypt.genSaltSync() + const user = new User({ name, email, password: bcrypt.hashSync(password, salt) }) + user.save() + res.status(201).json({ + success: true, + message: "User created", + id: user._id, + accessToken: user.accessToken, + name: user.name, + email: user.email + }) + } catch (error) { + res.status(400).json({ + success: false, + message: "Could not create user", + errors: error + }) + } +}) + +app.get("/secrets", authenticateUser, (req, res) => { + res.status(200).json({ + success: true, + response: { + secret: `This is a secret, accessible only to authenticated user: ${req.user.name}` + }, + message: "Secret retrieved successfully." + }) +}) +app.post("/sessions", async (req, res) => { + const { email, password } = req.body + try { + const user = await User.findOne({ email: email }) + + if (user && bcrypt.compareSync(password, user.password)) { + res.status(200).json({ + success: true, + response: { + userId: user._id, + accessToken: user.accessToken, + name: user.name + }, + message: "Login successful." + }) + } else { + res.status(401).json({ + success: false, + response: null, + message: "Login failed: Invalid email or password." + }) + } + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Server error during login." + }) + } +}) + +//Thoughts const thoughtSchema = new mongoose.Schema({ - id: Number, message: { type: String, required: true, @@ -35,6 +152,10 @@ const thoughtSchema = new mongoose.Schema({ createdAt: { type: Date, default: () => new Date() + }, + userId: { + type: String, + required: true } }) @@ -46,16 +167,16 @@ if (process.env.RESET_DB){ await Thought.deleteMany({}) for (const thought of thoughtData) { - const newThought = new Thought({ message: thought.message, hearts: thought.hearts || 0, - createdAt: thought.createdAt ? new Date(thought.createdAt) : new Date() + createdAt: thought.createdAt ? new Date(thought.createdAt) : new Date(), + userId: thought.userId || 'seed-user-id' }) await newThought.save() } } catch (error) { - console.error("Error seeding database:", error) + console.error(error) } } seedDatabase() @@ -81,11 +202,13 @@ app.get("/", (req, res) => { app.get("/thoughts", async (req, res) => { - const { hearts, message, page, limit, sort } = req.query + const { hearts, message, page, limit, sort, id } = req.query const query = {} - let filteredThoughts = [...thoughtData] + if (id) { + query._id = id; + } //Filter to get the messages with at least the amount of hearts that the user asks for if (hearts) { @@ -185,13 +308,12 @@ app.get("/thoughts/:id/", async (req, res) => { }) //Post endpoint - app.post("/thoughts", async (req, res) => { const {message} = req.body try { - const newThought = await new Thought ({message}).save() + const newThought = await new Thought ({message, userId}).save() res.status(201).json({ success: true, @@ -199,36 +321,55 @@ app.post("/thoughts", async (req, res) => { message: "Thought created successfully" }) } catch (error) { - res.status(500).json({ - success: false, - response: error, - message: "Failed to create thought." - }) + if (error.name === 'ValidationError') { + res.status(400).json({ + success: false, + response: error.errors, + message: "Validation failed for thought creation." + }) + } else { + res.status(500).json({ + success: false, + response: error, + message: "Failed to create thought." + }) + } } }) //Delete endpoint: delete a thought by ID - app.delete("/thoughts/:id", async (req, res) => { - const { id } = req.params + const { id } = req.params + const { userId } = req.body - try { - const deletedThought = await Thought.findByIdAndDelete(id) + try { + const thoughtToDelete = await Thought.findById(id) - if(!deletedThought) { - return res.status(404).json({ + if (!thoughtToDelete) { + return res.status(404).json({ + success: false, + response: null, + message:"Thought could not be found, can't delete." + }) + } + + if (thoughtToDelete.userId !==userId) { + return res.status(403).json({ success: false, response: null, - message: "Thought could not be found, can't delete." + message: "You are not authorized to delete this thought." }) } + + const deletedThought = await Thought.findByIdAndDelete(id) + res.status(200).json({ success: true, response: deletedThought, message: "Thought was successfully deleted." }) - } catch { + } catch (error) { res.status(500).json({ success: false, response: error, @@ -238,7 +379,6 @@ app.delete("/thoughts/:id", async (req, res) => { }) // Patch endpoint: update a thought by ID - app.patch("/thoughts/:id", async (req, res) => { const { id } = req.params @@ -273,11 +413,10 @@ app.patch("/thoughts/:id/like", async (req, res) => { const { id } = req.params try { - // Find the thought and increment its 'hearts' field by 1 const updatedThought = await Thought.findByIdAndUpdate( id, - { $inc: { hearts: 1 } }, // MongoDB operator to increment a field - { new: true } // Return the updated document + { $inc: { hearts: 1 } }, //mongoDB operator to increment a field + { new: true } ) if (!updatedThought) { @@ -295,7 +434,7 @@ app.patch("/thoughts/:id/like", async (req, res) => { }) } catch (error) { - res.status(400).json({ // Use 400 for bad ID format, 500 for other server errors + res.status(400).json({ success: false, response: error, message: "Failed to like thought due to invalid ID or server error." @@ -304,10 +443,9 @@ app.patch("/thoughts/:id/like", async (req, res) => { }) // Patch endpoint: update a thought by ID (for message and unlike) -// This endpoint specifically handles message updates and unliking. app.patch("/thoughts/:id", async (req, res) => { const { id } = req.params - const { message, unlike } = req.body + const { message, unlike, userId } = req.body try { const thought = await Thought.findById(id) @@ -321,19 +459,38 @@ app.patch("/thoughts/:id", async (req, res) => { } const updateFields = {} + let performUpdate = false + if (message !== undefined) { + + if(thought.userId !== undefined) { + return res.status(403).json({ + success: false, + response: null, + message: "You are not authorized to edit this thought's message.", + }) + } updateFields.message = message + performUpdate = true } let newHearts = thought.hearts if (unlike) { newHearts = Math.max(0, thought.hearts - 1) + if (newHearts !== thought.hearts) { + updateFields.hearts = newHearts + performUpdate = true + } } - if (newHearts !== thought.hearts || message !== undefined) { - updateFields.hearts = newHearts + if (!performUpdate) { + return res.status(200).json({ + success: true, + response: thought, + message: "No valid update fields provided, thought not modified." + }) } - + const updatedThought = await Thought.findByIdAndUpdate( id, updateFields, From cc3812ed3abfc69a91dd21cfccdf4f39de2cbba4 Mon Sep 17 00:00:00 2001 From: Idahel Date: Thu, 12 Jun 2025 13:47:44 +0200 Subject: [PATCH 5/8] trying to add authentication --- server.js | 137 +++++++++++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/server.js b/server.js index 8446ea1..54e7f38 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ import cors from "cors" -import express, { response } from "express" +import express from "express" import listEndpoints from 'express-list-endpoints' import mongoose from "mongoose" import crypto from "crypto" @@ -50,7 +50,15 @@ const User = model("User", userSchema) // Middleware for authenticating users via accessToken const authenticateUser = async (req, res, next) => { - const accessToken = req.header("Authorization") + const accessToken = req.header("Authorization")?.replace("Bearer ", "") + if (!accessToken) { + return res.status(401).json({ + success: false, + response: null, + message: "Unauthorized: No access token provided." + }) + } + try { const user = await User.findOne({ accessToken: accessToken }) @@ -73,26 +81,43 @@ const authenticateUser = async (req, res, next) => { } } -app.post("/users", (req, res) => { +app.post("/users", async (req, res) => { try { const { name, email, password } = req.body const salt = bcrypt.genSaltSync() const user = new User({ name, email, password: bcrypt.hashSync(password, salt) }) - user.save() + await user.save() + res.status(201).json({ success: true, - message: "User created", - id: user._id, - accessToken: user.accessToken, - name: user.name, - email: user.email + message: "User created successfully", + response: { + id: user._id, + accessToken: user.accessToken, + name: user.name, + email: user.email + } }) } catch (error) { - res.status(400).json({ - success: false, - message: "Could not create user", - errors: error - }) + if (error.code === 11000) { + res.status(409).json({ + success: false, + response: error, + message: "User with this name or email already exists." + }) + } else if (error.name === 'ValidationError') { + res.status(400).json({ + success: false, + response: error.errors, + message: "Validation failed for user creation." + }) + } else { + res.status(500).json({ + success: false, + response: error, + message: "Failed to create user." + }) + } } }) @@ -105,6 +130,7 @@ app.get("/secrets", authenticateUser, (req, res) => { message: "Secret retrieved successfully." }) }) + app.post("/sessions", async (req, res) => { const { email, password } = req.body try { @@ -207,7 +233,7 @@ app.get("/thoughts", async (req, res) => { const query = {} if (id) { - query._id = id; + query._id = id } //Filter to get the messages with at least the amount of hearts that the user asks for @@ -231,19 +257,21 @@ if (hearts) { //Sort the messages for date created and amount of hearts const sortOptions = {} -if (sort) { - if (sort === 'createdAt') { - sortOptions.createdAt = -1; // Newest first - } else if (sort === 'hearts') { - sortOptions.hearts = -1; // Most hearts first - } else { - return res.status(400).json({ - success: false, - response: null, - message: "Invalid 'sort' parameter. Valid options are 'createdAt' or 'hearts'." - }) + if (sort) { + if (sort === 'createdAt_desc' || sort === 'createdAt') { + sortOptions.createdAt = -1 + } else if (sort === 'createdAt_asc') { + sortOptions.createdAt = 1 + } else if (sort === 'hearts') { + sortOptions.hearts = -1 + } else { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid 'sort' parameter. Valid options are 'createdAt_desc', 'createdAt_asc', or 'hearts'." + }) + } } -} //Let the user choose to view a specific amount of thoughts per page and also to go between pages const pageNum = parseInt(page, 10) || 1 //Default to page 1 @@ -308,9 +336,10 @@ app.get("/thoughts/:id/", async (req, res) => { }) //Post endpoint -app.post("/thoughts", async (req, res) => { +app.post("/thoughts", authenticateUser, async (req, res) => { const {message} = req.body + const userId = req.user._id try { const newThought = await new Thought ({message, userId}).save() @@ -338,10 +367,10 @@ app.post("/thoughts", async (req, res) => { }) //Delete endpoint: delete a thought by ID -app.delete("/thoughts/:id", async (req, res) => { +app.delete("/thoughts/:id", authenticateUser, async (req, res) => { const { id } = req.params - const { userId } = req.body + const authenticatedUserId = req.user._id try { const thoughtToDelete = await Thought.findById(id) @@ -354,7 +383,7 @@ app.delete("/thoughts/:id", async (req, res) => { }) } - if (thoughtToDelete.userId !==userId) { + if (thoughtToDelete.userId.toString() !== authenticatedUserId.toString()) { return res.status(403).json({ success: false, response: null, @@ -379,43 +408,13 @@ app.delete("/thoughts/:id", async (req, res) => { }) // Patch endpoint: update a thought by ID -app.patch("/thoughts/:id", async (req, res) => { - - const { id } = req.params - const { newMessage } = req.body - - try { - const updatedThought = await Thought.findByIdAndUpdate ( id, { message: newMessage }, {new: true, runValidators: true}) - - if(!updatedThought) { - return res.status(404).json({ - success: false, - response: null, - message: "Thought could not be found." - }) - } - - res.status(200).json({ - success: true, - response: updatedThought, - message: "Thought was successfully updated" - }) - } catch (error) { - res.status(500).json({ - success: false, - response: error, - message: "Could not edit thought." - }) - } -}) - app.patch("/thoughts/:id/like", async (req, res) => { const { id } = req.params try { const updatedThought = await Thought.findByIdAndUpdate( id, - { $inc: { hearts: 1 } }, //mongoDB operator to increment a field + { $inc: { hearts: 1 } }, { new: true } ) @@ -442,10 +441,11 @@ app.patch("/thoughts/:id/like", async (req, res) => { } }) -// Patch endpoint: update a thought by ID (for message and unlike) -app.patch("/thoughts/:id", async (req, res) => { +// Patch endpoint: update a thought by ID (for message and unlike, now authenticated and authorized) +app.patch("/thoughts/:id", authenticateUser, async (req, res) => { const { id } = req.params - const { message, unlike, userId } = req.body + const { message, unlike } = req.body + const authenticatedUserId = req.user._id try { const thought = await Thought.findById(id) @@ -462,12 +462,11 @@ app.patch("/thoughts/:id", async (req, res) => { let performUpdate = false if (message !== undefined) { - - if(thought.userId !== undefined) { + if (thought.userId.toString() !== authenticatedUserId.toString()) { return res.status(403).json({ success: false, response: null, - message: "You are not authorized to edit this thought's message.", + message: "You are not authorized to edit this thought's message." }) } updateFields.message = message @@ -490,7 +489,7 @@ app.patch("/thoughts/:id", async (req, res) => { message: "No valid update fields provided, thought not modified." }) } - + const updatedThought = await Thought.findByIdAndUpdate( id, updateFields, From 14b07b3e12e70db3de77d0133f3bcc1ef8d01880 Mon Sep 17 00:00:00 2001 From: Idahel Date: Thu, 12 Jun 2025 14:03:20 +0200 Subject: [PATCH 6/8] updated the port due to issues in render --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5a1642b..a68bb69 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^6.0.0", "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", "express": "^4.17.3", From 96010a23ada66183f5d13f94ef8096ec1916bf20 Mon Sep 17 00:00:00 2001 From: Idahel Date: Wed, 18 Jun 2025 12:30:59 +0200 Subject: [PATCH 7/8] updated sessions endpoint to match users endpoint ID --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 54e7f38..8460b15 100644 --- a/server.js +++ b/server.js @@ -140,7 +140,7 @@ app.post("/sessions", async (req, res) => { res.status(200).json({ success: true, response: { - userId: user._id, + id: user._id, accessToken: user.accessToken, name: user.name }, From 9d6a6ba559406b172e7d367ea5bc97e2a68bbb5f Mon Sep 17 00:00:00 2001 From: Idahel Date: Wed, 18 Jun 2025 12:49:25 +0200 Subject: [PATCH 8/8] added render link --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 8460b15..e3ed406 100644 --- a/server.js +++ b/server.js @@ -14,7 +14,7 @@ mongoose.Promise = Promise // Defines the port the app will run on. Defaults to 8080, but can be overridden // when starting the server. Example command to overwrite PORT env variable value: // PORT=9000 npm start -const port = process.env.PORT || 9000 +const port = process.env.PORT || 8080 const app = express() // Add middlewares to enable cors and json body parsing